高频写入下ArrayList性能瓶颈源于反复扩容导致的数组复制,优化核心是避免中间扩容:预设初始容量、批量前调用ensureCapacity、分段预估容量,并禁用LinkedList替代。

高频写入场景下,ArrayList 的性能瓶颈主要来自反复扩容引发的数组复制和内存分配。优化核心是“避免中间扩容”,关键在容量预估与提前干预。
预设初始容量是最直接有效的手段
无参构造默认初始容量为 10,插入 10 万条数据会触发约 15 次 grow(10→15→22→33→49→73→109…),每次都要 System.arraycopy。直接指定初始容量可归零中间扩容:
- 已知数据量:用 new ArrayList(expectedSize),如
new ArrayList(100000) - 不确定但有上限:按上限预设,宁略大勿略小;多占一点内存远比多次拷贝划算
- JDK 8+ 延迟初始化不影响该策略——首次 add 前调用构造函数已锁定底层数组大小
批量写入前主动调用 ensureCapacity
适用于无法在构造时确定容量的场景(如流式读取、分页聚合):
- 在循环 add 前调用 list.ensureCapacity(minExpected),强制一次性扩到目标容量
- 该方法只在当前容量不足时才扩容,否则无开销,安全无副作用
- 实测显示:插入 10 万条数据,使用 ensureCapacity 可将耗时从 ~20ms 降至 ~7ms(JDK 17 环境)
规避隐式扩容陷阱
有些操作看似不增元素,实则悄悄触发扩容:
立即学习“Java免费学习笔记(深入)”;
- addAll(Collection) 内部会先 ensureCapacity(size + c.size()),但如果原 list 已接近满载,仍可能触发 grow
- 反模式:
list = new ArrayList(); list.addAll(sourceList);→ 若 sourceList 很大,而 list 初始为 10,仍要扩容 - 正解:改用
new ArrayList(sourceList)或先ensureCapacity(sourceList.size())
结合业务节奏做分段预估
对持续写入但总量不可控的场景(如日志缓冲、实时指标聚合),可采用动态预估策略:
- 按时间窗口或批次粒度预估:例如每秒最多写入 500 条,预留 5 秒缓冲 → 初始容量设为 2500
- 写入中监控 size / capacity 比值,当 >0.8 时提前 ensureCapacity(newCap),防突增打爆
- 避免用 LinkedList 替代——它无扩容成本,但随机访问和 CPU 缓存局部性极差,整体吞吐反而更低



















