扩容触发条件是添加元素前校验容量,当size+1超过elementdata.length时触发;典型场景包括add(e e)、addall及ensurecapacity显式调用。

ArrayList 的扩容机制是 Java 面试中高频出现、但很多人只记结论、不究原理的典型盲区。光知道“默认1.5倍扩容”远远不够——真正卡人的,是 何时触发、如何计算、线程安全边界、内存浪费与性能权衡 这些细节。
扩容触发的真实条件:不只是 add() 时才发生
扩容发生在元素写入前的容量校验阶段,核心逻辑在 add(E e) 和 addAll(Collection extends E> c) 等写入方法中调用的 ensureCapacityInternal() 里。关键点:
- 首次 add 时,若底层数组为
DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组),会直接扩容到 默认初始容量 10,而非“1.5 × 0” - 后续扩容判断依据是:
size + 1 > elementData.length,即“再加一个就放不下”,不是等真放不下了才扩 -
ensureCapacity(int minCapacity)是公开方法,外部可主动预扩容,避免多次自动扩容带来的数组复制开销
扩容计算的真实公式:不是简单 ×1.5,而是有下限保障
新容量计算由 grow(int minCapacity) 完成,逻辑比想象中严谨:
- 先尝试:newCapacity = oldCapacity + (oldCapacity >> 1) → 即 1.5 倍右移实现
- 但若 newCapacity 仍
- 若 newCapacity 溢出(超过
Integer.MAX_VALUE),则调用hugeCapacity(minCapacity),按需设为Integer.MAX_VALUE或MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
扩容过程中的真实代价:一次 grow = 一次数组复制 + 一次对象引用赋值
扩容本质是创建新数组 + System.arraycopy() 复制引用(不是深拷贝),重点在于:
- 复制耗时与当前
size成正比,不是与capacity相关;所以频繁 add 小量数据比一次性 add 大量数据更易触发多次复制 - 旧数组若无其他引用,会在下次 GC 时被回收;但若 list 生命周期长、扩容频繁,会产生较多短期垃圾
- 多线程环境下,ArrayList 本身不保证扩容线程安全:两个线程同时触发扩容,可能造成数据丢失或数组覆盖(即使 size++ 是原子的,但数组复制和引用赋值不是整体原子操作)
查漏补缺的关键验证点:别只背源码,要会推演场景
面试官常通过具体场景考察是否真懂。试试这几个问题能否秒答:
- new ArrayList(0).add("a") 后,capacity 是多少?→ 10(首次 add 触发默认初始化)
- ArrayList
list = new ArrayList(5); list.addAll(Arrays.asList(1,2,3,4,5,6)); 容量变化过程?→ 先设为 5,add 第6个时触发扩容,newCapacity = 5 + 2 = 7 - 如果连续 add 1000 个元素,大概触发几次扩容?→ 从 10 开始:10→15→22→33→49→73→109→163→244→366→549→823→1234,共 12 次
不复杂但容易忽略——扩容机制背后是空间换时间的经典权衡,理解它,不只是为了答对一道题,更是为了写出更可控的集合使用逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











