arraylist扩容时的数组复制是性能损耗主因,因高频大批量场景下反复触发17次grow(),累计复制远超10万元素,单次最坏o(n),加剧cpu、内存带宽与gc压力。

ArrayList 扩容时的数组复制是性能损耗的主要来源,不是因为“复制一次很慢”,而是它在高频、大批量场景下会反复发生,带来三重开销:CPU 时间、内存带宽占用和 GC 压力。
数组复制触发频繁扩容链
默认初始容量为 10,每次扩容按 1.5 倍增长(即 oldCapacity + (oldCapacity >> 1))。添加 10 万个元素时,实际会触发约 17 次 grow(),每次都要调用 Arrays.copyOf() 复制全部已有元素。例如:
- 第 1 次扩容:复制 10 个元素
- 第 2 次扩容:复制 15 个元素
- ……
- 第 17 次扩容:复制约 6.5 万个元素
累计复制元素总量远超 10 万,时间复杂度虽均摊为 O(1),但单次插入的最坏情况是 O(n),对响应敏感型接口(如 API 接口 RT 要求
内存与 GC 的隐性代价
每次扩容都会分配一块新数组,旧数组立即失去引用,变成待回收对象。在 Young GC 频繁触发的批量导入场景中,大量短命大数组会快速填满 Eden 区,引发:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 更频繁的 Minor GC
- 对象提前晋升到 Old 区(尤其当数组较大时)
- Full GC 风险上升,STW 时间拉长
线上曾有案例:一个日志聚合服务因未预设容量,批量写入时 Young GC 次数从每分钟 2 次飙升至 40+ 次,平均延迟翻倍。
复制过程本身不支持并发优化
System.arraycopy 是 JVM 内置的高效拷贝,但仍是同步、阻塞、逐元素搬运。它无法并行化,也不能跳过空位或压缩数据——哪怕你只 add 了 1 个元素却触发扩容,也要把整个旧数组完整搬过去。这对吞吐密集型任务(如实时计算流水线)构成硬性瓶颈。
避免复制开销的实用方式
核心思路是“让复制只发生一次,或根本不发生”:
- 已知规模:直接用 new ArrayList(expectedSize) 构造,底层数组一步到位
- 构造后才确定规模:调用 ensureCapacity(expectedSize),效果等同于构造时指定
- 批量添加:优先用 addAll(Collection),它内部会一次性检查总需容量,比循环 add 更省
- 极端场景:若 size 可能剧烈波动,考虑分段处理或切换为 LinkedList(仅限插入/删除远多于随机访问)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










