首先想想我们什么情况下会扩容,在向数组中添加元素的时候数组容量不够的情况下我们会进行扩容。
所以我们先从它的添加元素的方法说起。
add(int index,E element)
//在指定位置添加一个元素
public void add(int index, E element) {
rangeCheckForAdd(index);
//size+1表示要添加这个元素,ArrayList的容量至少为size+1
ensureCapacityInternal(size + 1);
// element放在数组的最后一个位置
elementData[index] = element;
//arrayList的元素数量加一
size++;
}
我们可以看到有ensureCapacityInternal()方法。确保数组容量在范围内。
ensureCapacityInternal()
// minCapacity表示ArrayList至少需要容量的大小
private void ensureCapacityInternal(int minCapacity) {
// 确定明确的容量
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
calculateCapacity()计算当前ArrayList的容量要扩展到多大。
calculateCapacity()
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果底层数组是默认容量的空数组,那就在默认容量和指定容量中取一个最大值,
// 当然这就意味着 如果底层数组是默认容量空数组,就保证了我要扩至少为10的容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
ensureExplicitCapacity
()
private void ensureExplicitCapacity(int minCapacity) {
// 修改次数加一
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
我们看到了grow()方法。这个方法就是所要用到的扩容方法。
grow()
首先获取当前容量oldCapacity,将当前容量的二进制数右移1位后再转为十进制(就是当前容量的1.5倍)就是扩展后的容量(newCapacity)。
如果新容量(newCapacity)比指定的容量(minCapacity)小,那就将指定容量作为新容量,否则新容量就是当前容量的1.5倍。
如果新容量比最大数组容量(MAX_ARRAY_SIZE,即2的31次方-9)还要大,就需要通过hugeCapacity方法进一步确定新容量了,否则新容量依然是当前容量的1.5倍。
hugeCapacity()
如果指定容量小于0,就抛一个内存溢出异常;
如果指定容量大于最大数组容量(MAX_ARRAY_SIZE,就是最大整数减8),就返回最大的整数2的31次方减一,否则就返回最大数组容量。
最大整数为2的31次方-1,最大数组容量为最大整数-8
为什么要减8 上面的注释是这样解释的:
一些VM在数组中保留一些标题字。尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过VM限制。
回到grow()方法的最后一行。
用了Arrays.copyOf(elementData,newCapacity)方法,将数组和新的容量传进去。
将elementData数组中的元素复制到新的数组里,复制newCapacity个。
总结:
我们初始化一个ArrayList 集合还没有添加元素时,其实它是个空数组,只有当我们添加第一个元素时,内部会调用扩容方法并返回最小容量10,也就是说ArrayList 初始化容量为10。
当前数组长度小于最小容量的长度时(前期容量是10,当添加第11个元素时就就扩容)ArrayList 扩容的真正计算是在grow()里面,新数组大小是旧数组的1.5倍,如果扩容后的新数组大小还是小于最小容量,那新数组的大小就是最小容量的大小,后面会调用一个Arrays.copyof方法,这个方法是真正实现扩容的步骤。