1.简介:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- 继承了AbstractList,提供相关的修改、删除、遍历等功能
- 实现了RandomAccess接口,提供了随机访问访问功能,即可以通过元素序号访问元素
- 实习Cloneable接口,可以克隆
- 实现了Serializable接口,实现了序列化的功能
其他:ArrayList不是线程安全的,多线程可以考虑使用Vector或者CopyOnWriteArrayList
特点:
1. 不是线程安全的,不是线程同步的。
2.允许null在内的所有元素。
3.ArrayList中存放顺序和添加顺序是一致的。并且可重复元素。
4. ArrayList实现了可变大小的数组。
2.ArrayList属性:
private static final long serialVersionUID = 8683452581122892189L;
//数组的初始容量
private static final int DEFAULT_CAPACITY = 10;
//当用户指定容量为0时
//elementData=EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
//当用户未指定容量时
//elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//当用户第一次添加元素时,会扩容为10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//transient修饰代表不会被序列化
//ArrayList用该数组保存数据
transient Object[] elementData; // non-private to simplify nested class access
private int size;
3.构造函数
(1) 由用户指定容量的构造函数
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
当 initialCapacity=0时,elementData = EMPTY_ELEMENTDATA;
(2)无参构造函数
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.重要方法:
1.add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
线程安全:我们注意到elementData[size++] = e;这条语句其实就是添加元素的关键,其实这条语句可以拆成两条语句来执行也就是elementData[size] = e 和 size++,试想一下,如果在多线程访问同一个list的时候,线程A执行到elementData[size] = e时,时间片到了让出CPU,此时线程B执行,当线程B执行到elementData[size] = e此时size还没有加一,(假设此时size=10)也就是说线程A在list[10]位置上添加的元素被线程B添加的元素覆盖了,然后A,B两个线程都执行size++;这样造成的影响就是size变成12了,但是在第11个位置上却没有元素也就是说list[11]=null
add方法首先调用ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
可以看出,如果ArrayList是通过无参构造方法初始且第一次add,此时minCapacity=1,然后minCapacity变为10,然后调用
ensureExplicitCapacity(10)
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果minCapacity>当前数组的长度才进行扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
Arrays.copyof调用的是本地方法 System.arraycopy() ;
Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组
System.arraycopy() 源码如下:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
参数说明:
src:源对象
srcPos:源数组中的起始位置
dest:目标数组对象
destPos:目标数据中的起始位置
length:要拷贝的数组元素的数量
grow分析:先把新容量变为旧容量1.5倍,如果新容量还是小于指定容量,则新容量=指定容量,如果新容量大于最大容量,则看minCapacity与MAX_ARRAY_SIZE的大小,如果minCapacity>MAX_ARRAY_SIZE则新容量为Integer.MAX_VALUE否则新容量为MAX_ARRAY_SIZE
注意:如果无参构造初始的ArrayList,则在第一次添加元素时就进行扩容,第一次扩容至10,直到容量大于10才进行1.5倍扩容,如果不是无参构造初始的ArrayList,
总结:add方法: add-> ensureCapacityInternal(是否时无参构造初始的ArrayList)->ensureExplicitCapacity(所需容量是否大于当前容量)->grow(扩容)
2.remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
remove 中主要是将之后的元素都向前一位移动,然后将最后一位的值设置为空。最后,返回已经删除的值。
3.indexof方法
.indexOf方法--查找下标
/**
* 查找下标, 如果为null,直接和null比较,返回下标
*/
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
/**
* 查找最后出现的下标,从大往下循环查找
*/
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
4.总结:
与LinkedList区别
1. ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
2. 对于随机访问,ArrayList优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问。而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
3. 对于插入和删除操作,LinkedList优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。
4. LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。