arraylist扩容在add()前检查size+1>elementdata.length,满足即触发;grow()按1.5倍(oldcapacity+oldcapacity>>1)计算新容量,不足则取mincapacity,超限走hugecapacity;最终由arrays.copyof调用system.arraycopy完成浅拷贝。

Java ArrayList 的自动扩容不是“悄悄变大”,而是每次容量不够时,主动创建新数组、搬运旧数据、替换引用——整个过程由 add() 触发,grow() 执行,Arrays.copyOf() 完成拷贝。
扩容什么时候发生?
关键判断只有一行:size + 1 > elementData.length。
也就是说,当你准备加第 n+1 个元素,而当前数组最多只能存 n 个时,扩容立刻启动。
- 首次添加元素(无参构造):底层数组是空的(
EMPTY_ELEMENTDATA),直接扩容到默认容量 10 - 后续添加:只要
size等于数组长度,下一次add()就会触发扩容 - 批量添加(如
addAll()):会提前计算总需容量,尽量一次性到位,避免反复扩容
新容量怎么算?为什么是 1.5 倍?
核心公式是:newCapacity = oldCapacity + (oldCapacity >> 1),也就是旧容量加上它的一半(位运算 >> 比除法更快)。
- 初始容量 10 → 扩容后为 10 + 5 = 15
- 容量 15 → 扩容后为 15 + 7 = 22(15 >> 1 = 7,不是 7.5)
- 容量 22 → 扩容后为 22 + 11 = 33
选 1.5 倍是权衡结果:倍数太小(如 1.1)会导致频繁扩容、拷贝开销大;太大(如 2)又浪费内存。1.5 在空间利用率和扩容频率之间取得较好平衡。
数据是怎么拷贝过去的?
拷贝动作由 Arrays.copyOf(elementData, newCapacity) 完成,它内部调用的是本地方法 System.arraycopy(),效率极高。
- 不是逐个 for 循环赋值,而是底层内存块级复制,接近硬件速度
- 拷贝的是对象引用(不是对象本身),所以是浅拷贝:原数组和新数组里存的仍是同一组对象地址
- 旧数组失去引用后,会在合适时机被 GC 回收
有没有特殊情况要处理?
有,扩容逻辑包含两层兜底检查:
-
最小容量不足:比如一次
addAll()要加 50 个元素,但按 1.5 倍算出来的新容量只有 33,就不够——此时直接把新容量设为 50 -
容量超限:如果计算出的新容量超过
Integer.MAX_VALUE - 8(JVM 数组最大安全长度),就会进入hugeCapacity()处理,要么抛 OOM,要么设为最大允许值
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











