两个构造器
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性能要比有参构造的稍微高那么一点点,毕竟少了复制数组的操作。
本文深入探讨了Java中ArrayList的两种构造器(无参与指定初始容量)在元素添加过程中的扩容机制差异。无参构造器在首次添加元素时将容量预设为10,后续9次添加无需扩容;而指定初始容量为0的构造器则在每次添加元素时都会进行扩容。
2221

被折叠的 条评论
为什么被折叠?



