ensureCapacity在大数据插入前调用可避免多次数组复制,但需准确预估总量;它适用于构造后才获知规模的场景,参数应直接传最终预期元素总数,而非当前size加增量。

直接说结论:ensureCapacity 在大数据插入前调用,能避免多次数组复制,但必须配合合理预估——它不解决“估算不准”的问题,只放大你预估的准确性。
什么时候该用 ensureCapacity 而不是 new ArrayList(int)
两者都设初始容量,但行为不同:
-
new ArrayList(1000):构造时就分配长度为 1000 的数组,elementData.length == 1000,后续 add 不触发第一次扩容 -
new ArrayList()+ensureCapacity(1000):构造时用的是共享空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,调用ensureCapacity后才真正分配长度 1000 的数组(跳过默认首次扩到 10 的中间步骤) - 关键区别:如果你在构造后、add 前才拿到数据规模(比如从 DB 查询 count 或流式读取 header),
ensureCapacity是唯一能“补救”的时机;而构造函数要求容量已知
ensureCapacity(minCapacity) 的 minCapacity 怎么算才不翻车
常见错误是传 list.size() + N,但这是错的——ensureCapacity 看的是底层数组长度(elementData.length),不是当前元素个数(size)。它要确保“至少能容纳 minCapacity 个元素”,所以应直接传最终预期总数量:
- 要插入 5000 条数据?传
5000,不是list.size() + 5000(除非 list 已有内容) - 循环中分批 add?别在每次循环里调
ensureCapacity,只在最开始调一次,传总目标量 - 如果数据量动态变化(如流式解析 JSON 数组),宁可略高估(+10%),也不要低估——低估一次就白干,触发扩容仍要复制
为什么 ensureCapacity 有时没效果?三个典型陷阱
现象:调了 list.ensureCapacity(10000),但跑起来还是看到多次 grow() 日志或 GC 尖峰。原因往往在这:
- 调用太晚:在第一次
add()之后才调——此时ensureCapacityInternal已被触发,内部数组已扩到 10,再调ensureCapacity只能基于 10 做 1.5 倍增长(10→15→22→33…),无法一步到位 - 传参小于当前
elementData.length:比如 list 已有 8000 元素且容量是 12000,再传10000就完全无效(minCapacity 时方法直接返回) - 和
trimToSize()冲突:如果之前调过trimToSize()把容量压到size,再调ensureCapacity才有效;否则可能被“压扁”后又反复扩
大数据场景下,ensureCapacity 和 grow 的实际开销在哪
重点不是 ensureCapacity 本身耗时(它只是比较+分支),而是它背后触发的 grow() ——这才是性能瓶颈所在:
-
grow()必然执行Arrays.copyOf(elementData, newCapacity),本质是System.arraycopy,时间复杂度 O(n),n 是当前元素个数 - 新容量计算:JDK 8+ 是
oldCapacity + (oldCapacity >> 1)(即 ×1.5),不是四舍五入,比如 10→15、15→22、22→33……所以预设 10000 比预设 9999 更省一次扩容 - 内存压力:一次复制 100 万字符串引用,会瞬间申请一块大内存,容易触发 Young GC;若预估过大(比如传 1000 万但只存 10 万),浪费堆空间,还可能引发 Full GC
最易被忽略的一点:ensureCapacity 不改变 size,也不初始化元素——它只管底层数组长度。如果你依赖 get(i) 随机访问未 add 过的位置,依然会抛 IndexOutOfBoundsException,这点和数组声明 new String[1000] 不同。











