Java8中ArrayList的扩容机制
准备
首先要清楚ArrayList的容量的概念
The array buffer into which the elements of the ArrayList are stored. The capacity of the ArrayList is the length of this array buffer. Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA will be expanded to DEFAULT_CAPACITY when the first element is added
这是jdk8源码中对其elementData属性给出的doc解释,意思就是说这个elementData是存储 ArrayList 元素的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度。这里注意ArrayList的容量并不是size属性所对应的值。
The size of the ArrayList (the number of elements it contains).
这是官方对size属性给出的解释,size代表的是ArrayList中包含的元素的个数,从后面的例子中就可以看出差别
先看结论
- 当使用add方法添加元素时,list扩容后的大小是上一次的大小的1.5倍(当然这里的扩容指的是经过第一次扩容后容量为10之后的再发生的扩容行为)。
- 当使用addAll方法添加元素时,list会进行一次性扩容,不会因为扩容后容量不够而再次扩容,最后一个容量为16的就是一个例子,他不会扩容到15然后再扩容到15 + 15>>1 = 22,而是一次到位,也就是说,使用addAll方法,没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量的1.5 倍, 实际元素个数)
使用add(E e) 方法添加
public static void main(String[] args) {
ArrayList<Integer> list4 = new ArrayList<>();
for (int i = 1; i <= 11; i++) {
list4.add(i);
System.out.println("第"+i+"次添加后list4的容量:"+length(list4));
}
}
public static int length(ArrayList<Integer> list) {
try {
Field field = ArrayList.class.getDeclaredField("elementData");
field.setAccessible(true);
Object[] elementData = (Object[]) field.get(list);
return elementData.length;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
当list第一次调用add要向其中添加一个数字0的时候,ArrayList底层会先确保在当前的size+1的情况下内部容量是否够用,够用就放进去,不够就扩容。
第一次扩容因为elementData为一个长度为0的空数组,下一步会
拿着DEFAULT_CAPACITY(10)和当前size+1中的较大者去进行扩容,扩容前判断这个较大的数是否大于elementData的长度,如果大于就扩容,第一次扩容时,实现是这个样子
然后将这个元素放入对应的位置,并且size+1,下图为此时的elementData和list,从这里就可以看出来list和elementData的区别,从而理解容量的概念。
当第十一次add时这时当前的size为10
这个时候这个方法就会在默认容量和size+1个选择较大的数字为下一步扩容做准备,进入grow方法,进行一个具体的扩容操作
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);
当执行完复制后可以看到elementData的长度扩大为15(即10的1.5倍 ),然后再将元素放入相应的位置上。
控制台也看到第十一次添加后list的容量为15。
使用addAll(Collection<? extends E> c)方法添加
先看一下结果
这里为什么会是11呢?让我们进入debug查看一下
同样是进入这个方法这个时候取;两者中较大者11,接着进行下一步,
这里看得出来计算出来新的容量为11
打印出来的也就是11
那么写一个这样的例子
这个时候就可以看出结论来,
总结一下:
- 当使用add方法添加元素时,list扩容后的大小是上一次的大小的1.5倍(当然这里的扩容指的是经过第一次扩容后容量为10之后的再发生的扩容行为)。
- 当使用addAll方法添加元素时,list会进行一次性扩容,不会因为扩容后容量不够而再次扩容,最后一个容量为16的就是一个例子,他不会扩容到15然后再扩容到15 + 15>>1 = 22,而是一次到位,也就是说,使用addAll方法,没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量的1.5 倍, 实际元素个数)
|