调用 ensureCapacity 本身不优化性能,真正起效的是构造时指定初始容量;它仅适用于已创建且未填满、又预知后续将批量插入的特殊场景。

直接说结论:调用 ensureCapacity 本身不优化性能,真正起效的是在构造时就指定初始容量;ensureCapacity 只适合“已创建但尚未填满、又预知后续会猛增”的特殊场景。
为什么 ensureCapacity 很少被需要
多数人误以为调用 ensureCapacity 能“提前扩容”,从而避免 add 过程中的复制开销。实际上:
-
ensureCapacity是 public 方法,但 ArrayList 内部 add 流程走的是私有方法ensureCapacityInternal—— 它和ensureCapacity逻辑并不等价 - 即使你手动调用
list.ensureCapacity(1000),如果此时elementData已是长度为 10 的数组,它仍会触发一次 grow(分配新数组 + 复制),而这不是“免费扩容” - 更关键的是:它无法绕过
ensureCapacityInternal对首次添加的强制兜底(比如无参构造后第一次add必然走到 minCapacity=10)
ensureCapacity 的真实适用场景
它只在一种情况下值得用:ArrayList 已存在、当前 size 很小,但你知道接下来要批量 addAll 或循环插入大量元素,且无法修改构造逻辑(例如该 List 是别人传进来的参数)。
- 典型例子:
void process(List<string> output) { output.ensureCapacity(5000); for (int i = 0; i </string> - 此时若不调用,可能经历 10 → 15 → 22 → 33 → … 多次 grow,而一次
ensureCapacity(5000)直接跳到满足需求的最小容量(内部仍按 1.5 倍策略计算,但至少保底到 5000) - 注意:如果传入的
output是new ArrayList(100),那ensureCapacity(5000)会立刻触发扩容;如果是new ArrayList(),则先走首次扩容到 10,再 grow 到 ≥5000
比 ensureCapacity 更有效、更常用的方案
99% 的性能问题,靠 ensureCapacity 解决不了,靠它前面的构造器就能根治:
- 已知最终 size ≈ N:直接用
new ArrayList(N),比如new ArrayList(10000)—— 这样前 N 次 add 零扩容 - 不确定精确值但有下限:取预估上限,宁大勿小,比如预估 8k~12k,就用
new ArrayList(12000) - 如果用的是 Guava 或 Apache Commons,注意它们的工具类(如
Lists.newArrayListWithCapacity)底层也是调这个构造器,不是 magic - 反例:写成
new ArrayList()然后在循环里反复ensureCapacity,等于把扩容压力从 add 内部转移到了你自己的代码里,还多一次方法调用开销
容易被忽略的细节:ensureCapacity 不保证容量正好等于参数
ensureCapacity 的行为由 grow() 方法决定,而 grow 的实际新容量是:max(oldCapacity + (oldCapacity >> 1), minCapacity)。
- 当前容量是 10,调
ensureCapacity(12)→ 新容量 = max(15, 12) = 15 - 当前容量是 10,调
ensureCapacity(20)→ 新容量 = max(15, 20) = 20 - 所以它不是“设成多少就是多少”,而是“至少达到多少”,且优先走 1.5 倍策略
- 这也意味着:如果你在容量为 10 时反复调
ensureCapacity(11)、ensureCapacity(12)……毫无意义,只会反复触发相同扩容流程
真正影响性能的,从来不是某一次 ensureCapacity 调用,而是初始容量是否匹配数据规模。把注意力放在构造时刻,比在运行时补救更直接、更可靠。


















