transient Object[] elementData; //数组容量
private int size; //实际存的元素个数
下文将会频繁提及它们。
构造方法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);
}
}
传入初始值,则直接生成一个相应长度Object[]数组,此时size值是多少呢?
由于没有赋值,则仍旧使用实例化时的值,即0。
构造方法2:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
......
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
将elementData赋值为一个空的object[]数组,此时size=0;
构造方法3:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
传入参数为一个集合对象,首先将集合转化为数组,然后使用系统函数将该数组copy到elementData,即完成了赋值。此时size=elementData.length 即集合对象的长度。
下面看下add方法:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
增加一个元素,首先确保容量正确。
将当前的size+1传给calculateCapacity方法,如果数组中不存在元素,取size+1与10相比的最大值,返回。否则直接返回size+1。
然后size+1的值传入ensureExplicitCapacity 方法。如果该值大于实际目前数组中元素个数,说明当前空间不够了,需要扩容。否则除了增加modCount值后,不进行更多操作。modCount的作用我们稍后再说。
我们看下grow方法
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先对现有容量增加50%,去其与所需容量的最大值,如果该值大于Integer.MAX_VALUE - 8,则返回Integer.MAX_VALUE。所以一般所需容量小于现有容量的1.5倍,则使用1.5倍值,否则使用所需容量值,来新建数组并将旧数据copy过来。
下面看下get 方法:
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
private void rangeCheck(int index) {
if (index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
首先进行下标越界检查,然后通过下标返回该元素。
下面看下set方法:
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
可以看到是将index对应的值修改后,返回旧值。
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;
}
可以看到是使用了 System.arraycopy将index后面的元素都copy往前移一位,将旧值返回。
可见删除元素代价较大,而且元素越靠前,所要移动的数据就越多。
在指定位置新增元素add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
可见是通过System.arraycopy,将数据进行copy,代价较高。
总结如下,实例化:用无参方法初始化时,只分配了一个空数组。使用容量参数初始化时,分配一个参数大小的数组。使用集合作为参数初始化时,将集合转为数组,copy到新数组。
扩容机制是什么?
当数组元素为0时,第一次添加元素,容量变为10。此后add元素后所需容量大于现有容量时,进行扩容,扩容为1.5倍,如果还不够,则使用当前元素总数为数组长度。
ArrayList的优缺点是什么?
优点:可以根据下标快速找到元素(跟LinkedList相比),可以自动扩容(跟数组相比)。
缺点:1.指定下标增删数据,需要使用system.arraycopy 本地方法将该下标以后的数据进行大面积copy,效率较低。2.数组中可能会存在空元素,造成一定的空间浪费。
ArrayList使用场景有哪些?
适用于需要根据下标查询的可变长数组的情况。避免在需要对元素大量增删情况下使用(对列尾部增删无限制)。
以上。