两个构造器
ArrayList
的以下属性,跟扩容有关。
//无参构造时,初始容量大小
private static final int DEFAULT_CAPACITY = 10;
//存放ArrayList元素的数组
transient Object[] elementData;
//ArrayList的大小
private int size;
//传参为0时构造的数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//无参构造时的“空”数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
ArrayList
一共有三个构造器,这里主要提到前两个,即无参的和传入一个整数的构造器。
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
无参的很简单,直接将DEFAULTCAPACITY_EMPTY_ELEMENTDATA
赋给元素数组。
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);
}
}
有参的也很简单,传入的参数大于0时,创建一个新的Object
数组。等于0时,将EMPTY_ELEMENTDATA
赋给元素数组,小于0时,直接抛异常。
这两种方式创建的ArrayList
在扩容的时候,会有细微的区别。
以下源码基于JDK 1.8
。
使用无参构造器的扩容机制
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
执行add
方法,首先会调用ensureCapacityInteral(int minCapacity)
方法。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这个方法的参数是minCapacity
,最小容量,此时minCapacity=size+1=0+1=1
。这个方法又会调用ensureExplicitCapacity(int minCapactiy)
方法。在这之前会调用calculateCapacity(elementData, minCapacity)
方法,利用其返回值。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
这个方法,传入的参数是elementData
和minCapactiy
,如果数组是由无参构造器建造的,那么就返回默认数组大小DEFAULT_CAPACITY
和当前最小容量minCapacity
的较大值。此时minCapacity=1
,默认为10,所以返回的是10。
所以,当由无参构造器构造时,如果minCapacity<10
,就返回10;大于10,则返回minCapacity
,也就是当前容量+1(即size+1)。
而有参构造器构成时,总是返回minCapacity
,也即(size+1)。
那么此时ensureExplicitCapacity
方法的入参,就是10,
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
由于执行了添加操作,所以modCount++
(这个参数在迭代器的hasNext()
和next()
会用到,可能会抛出ConcurrentModificationException
),接着判断minCapacity和当前数组长度的关系,此时minCapacity=10
,数组长度为0,minCapacity-element.length>0
,所以要执行grow(int 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);
}
oldCapacity
=0,newCapacity=oldCapacity*1.5
还是=0,所以newCapacity-minCapacity=0-1<0
,把newCapacity
设置为minCapacity
,然后调用Arrays.copyOf
方法,将原数组扩容至newCapacity
,也就是10。
最后通过elementData[size++] = e
,完成添加,此时size=1。
第2次(一直到第10次)添加的时候,还是先调用ensureCapacityInteral(size+1)
,此时传入的参数为2。
然后调用calculateCapacity(elementData, minCapacity=2)
,2<默认值10,还是返回10。
在ensureExplicitCapacity
中,由于此时数组的容量是10,而10-10=0,所以不执行grow
方法。
那么直接就是elementData[size++] = e
,size=2。
第11次添加的时候,依然调用ensureCapacityInteral(size+1)
,此时传入的参数是11。
然后调用calculateCapacity(elementData, minCapacity=11)
,11>默认值10,返回11。
在ensureExplicitCapacity
中,由于此时数组的容量是10,而11-10>0,所以又要进行扩容了。
grow
方法,此时传入的参数是11,oldCapacity
是10,newCapacity
是15,newCapacity-minCapacity=4>0
,直接复制数组,长度由10增加到15,最后完成添加。
此后以此类推。
使用有参构造器的扩容机制
如果是有参构造的,那么elementData=EMPTY_ELEMENTDATA
,add
的时候,依然会先调用ensureCapacityInteral(size+1)
,传入的参数是1。
在calculateCapacity
中,此时elementData
并不等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA
,条件不成立,所以直接返回minCapacity
也就是1。
那么ensureExplicitCapacity
的入参,就是1,此时1-length=1>0,所以执行grow
方法。
grow
方法中,旧容量为0,新容量还是为0,newCapacity-minCapacity<0
,触发newCapacity=minCapacity
,新容量=1,然后进行数组复制,添加成功。
第2次添加的时候,ensureCapacityInteral(size+1)
,传入的是2。
calculateCapacity
始终返回minCapacity
,所以是2。
ensureExplicitCapacity
的入参,是2,2-1>0,又要执行grow
方法。
grow
方法中,旧容量为1,新容量为1,此时1-1=0,1-2<0,新容量=2,然后添加。
之后的每一次添加,传入的minCapacity
都会大于elementData.length
,所以每次添加都要执行grow
。
区别
总的来说:
使用无参构造的ArrayList
,在第一次添加后,会直接把容量扩容至10,接下来的2~10次添加,数组都不会扩容。之后一旦容量不够,就会扩容至原来的1.5倍。
而使用有参(initalCapacity=0)
构造的ArrayList
,每一次添加,都会进行扩容。
这么看来,当ArrayList
存储的元素数量小于等于10时,无参构造的ArrayList
性能要比有参构造的稍微高那么一点点,毕竟少了复制数组的操作。