ArrayList的扩容机制体现在当数组容量不足以容纳新元素时自动进行扩容。
当添加新元素而导致当前数组容量达到最大时,ArrayList会创建一个新的更大的数组,并将原数组中的内容复制到新创建的数组中去。先来看看ArrayList的初始化方式。
ArrayList的初始化方式
通过查看ArrayList源码的方式我们可以发现ArrayList有三种初始化方式,分别是:
1.无参初始化
2.带参初始化
可以发现,带参初始化有两种方式:
-
第一种就是指定数组初始化容量大小,如果指定值为负数,会抛出IllegalArgumentException异常;如果指定值为0,那么还是以ArrayList自定义的初始化大小来初始化数组容量。
-
第二种就是通过现在集合c来初始化,通过迭代器来构建和集合c一样的元素的ArrayList
ArrayList扩容机制
了解了ArrayList的初始化方式后,再来看看ArrayList是怎么扩容的:
使用ArrayList添加元素时,一般有以下几个方法作为备选:
一般我经常使用前两个,后两个用的很少,通过指定集合以迭代器的形式添加元素到数组。
以add(Integer e)为例,查看他的源码:
不难发现,其底层是通过调用add(e, elementDate, size)这个方法来实现添加元素到数组末尾的,再查看该方法源码:
可以看出,如果我们当前数组的容量和数组缓冲区的容量相等,那么就使用grow()方法使其扩容,elementData是什么?
回到grow()方法:
通过调用grow(int minCapacity)方法来扩容:
这里通过源码可以发现又涉及到了两个方法(不得不感叹这底层实现真是一环套一环!):copyOf()和newCapacity(int minCapacity),第一个方法很好理解,顾名思义,通过该方法的两个入参来复制数据以便创建新的数组,第二个呢?我们继续查看该方法源码:
好像快到尽头了,梳理一下newCapacity方法的逻辑:
-
计算当前数组容量大小
-
计算新数组的容量大小,通过位运算操作将oldCapacity右移一位,相当于oldCapacity/2,运算结果就是将新容量更新为就容量的1.5倍(PS:如果oldCapacity为偶数,则新增容量为1.5倍;若为奇数,新增容量是1.5倍左右。11+11/2=16会丢掉小数后数据)
-
如果新容量小于所需最小容量,则需要进行额外的检查:
-
如果当前数组是默认空数组,则返回DEFAULT_CAPACITY和minCapacity中的最大值
-
如果所需最小容量小于0(表示溢出),则抛出OutOfMemoryError异常
-
否则,返回minCapacity
-
-
如果新容量未超出数组最大容量MAX_ARRAY_SIZE,返回newCapacity作为新数组的容量大小
-
如果新容量超出了数组最大容量,则调用hugeCapacity(int minCapacity)方法处理边界情况
查看hugeCapacity(int minCapacity)方法源码:
可以看出:
-
入参minCapacity小于0时,和newCapacity方法一样为溢出情况,抛出OutOfMemoryError异常。
-
若minCapacity小于MAX_ARRAY_SIZE,说明容量大小仍在合理范围,返回MAX_ARRAY_SIZE表示可容纳最大数量。
-
若大于,说明容量过大,因此会返回Integer.MAX_VALUE,表示int类型的最大值。
举个例子:
public class ArrayListTest { public static void main(String[] args) { ArrayList<Integer> test = new ArrayList<>(5); for(int i = 0; i< 5; i++){ test.add(i); } test.add(5); } }
当执行test.add(5)时,就需要ArrayList的扩容机制给test扩容了,按照我们之前梳理的逻辑来看:
-
执行的方法链为:add(5) -> add(5, elementData, 5) -> grow() -> grow(5 +1) -> newCapacity(6)
-
newCapacity(6)方法中,oldCapacity=5,newCapacity=5+5/2=7,minCapacity=6
-
执行判断逻辑:newCapacity - minCapacity > 0且 newCapacity - MAX_ARRAY_SIZE < 0,因此直接返回newCapacity
至此,终于看完了ArrayList扩容机制的一个完整过程,若有疑问或不当之处,欢迎指出,共同进步!