ArrayList扩容核心路径是add→grow,三步:检测容量、计算新容量(old*1.5)、复制数组;默认构造首次add扩容至10,new ArrayList(0)则从1开始低效扩容。

直接看 add 方法入口,再顺藤摸到 grow(),这是理解 ArrayList 动态扩容最短也最准的路径。
核心逻辑就三步:检测容量 → 计算新容量 → 复制数组
ArrayList 扩容的关键源码位置(JDK 8+)
add(E e) 是触发扩容的起点:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length) // 关键判断:size 已等于数组长度
elementData = grow(); // 容量不足,进扩容
elementData[s] = e;
size = s + 1;
}真正干活的是 grow() 方法:
立即学习“Java免费学习笔记(深入)”;
private Object[] grow() {
return grow(size + 1); // 至少需要容纳 size+1 个元素
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 等价于 old * 1.5
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 防止算出来还不够用,兜底拉到 minCapacity
return elementData = Arrays.copyOf(elementData, newCapacity);
}注意两个细节:
-
oldCapacity >> 1是位运算,比oldCapacity / 2更快; -
Arrays.copyOf内部调用System.arraycopy,是 native 方法,高效但有开销。
不同构造方式对首次扩容的影响
构造方式决定了 elementData 初始指向哪个空数组,进而影响第一次 add 的行为:
new ArrayList<>()
→elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA
→ 第一次add时,grow(1)会被修正为grow(10),直接分配长度为 10 的数组。new ArrayList<>(0)
→elementData = EMPTY_ELEMENTDATA
→ 第一次add按常规逻辑走,oldCapacity=0,newCapacity=0,最终设为minCapacity=1,后续扩容节奏变成 1→2→3→4→6→9→13… 频繁复制。new ArrayList<>(n)(n > 0)
→ 直接创建长度为 n 的数组,跳过首次扩容,适合预估数据量的场景。
如何验证扩容行为?
可以用反射读取 elementData.length,或借助 ArrayList.size() 和 ArrayList 实际内存占用对比观察。简单验证示例:
ArrayList<String> list = new ArrayList<>();
System.out.println("初始容量: " + getCapacity(list)); // 0(因懒加载)
list.add("a");
System.out.println("add 1 次后: " + getCapacity(list)); // 10
list.add("b");
System.out.println("add 2 次后: " + getCapacity(list)); // 还是 10(没触发扩容)
// …直到第 11 次 add,才会 grow 到 15(getCapacity 可通过反射获取 elementData.length,仅用于调试)
不复杂但容易忽略


















