减少ArrayList内存浪费的关键是在扩容前控制容量增长路径,即合理设置初始容量、避免默认10引发的多次扩容、预估数量反推合适初始值,并在批量添加前调用ensureCapacity。

要减少 ArrayList 扩容带来的内存浪费,关键不是“怎么计算扩容”,而是**在扩容发生前就控制容量增长路径**——因为扩容本身固定按 1.5 倍增长(oldCapacity + (oldCapacity >> 1)),无法修改;真正可优化的是初始容量设定与对扩容边界的预判。
明确 size 和 capacity 的区别
很多内存浪费源于混淆这两个概念:
- size:当前实际存了多少个元素(比如 add 了 87 个,size 就是 87)
-
capacity:底层数组
elementData的长度(比如数组长度是 128,capacity 就是 128)
浪费 = capacity - size。若 capacity 是 128 而只存了 90 个元素,就空着 38 个位置——这本身不致命,但若因初始容量太小、被迫经历多次 10→15→22→33→49→73→109→128 这类阶梯式扩容,中间每一步都伴随一次数组拷贝+旧数组丢弃,累计浪费远超单次冗余。
用预估数量反推“够用且不过剩”的初始容量
不靠猜,用简单数学避开前几轮低效扩容:
立即学习“Java免费学习笔记(深入)”;
- 如果确定要存 N 个元素,直接设初始容量为 N(最简策略)
- 若数据量有波动(比如 ±10%),可设为 N × 1.1 向上取整,例如 N=950 → 初始容量设 1050
- 避免设成刚好卡在扩容临界点的数,比如预估 1000 个却设 1000,而 1000 经过几次 1.5 倍增长后可能跳到 1500、2250……造成明显冗余;设 1100 反而更贴近真实需求,后续大概率无需再扩
警惕“默认 10”引发的雪球效应
无参构造(new ArrayList())从 10 开始,按 1.5 倍增长序列是:
10 → 15 → 22 → 33 → 49 → 73 → 109 → 163 → 244 → 366 → 549 → 823 → 1234 …
存满 1000 个元素,会触发 12 次扩容,产生至少 12 次 Arrays.copyOf,且最后一次 capacity 达 1234,浪费 234 个空间——而一步到位设 new ArrayList(1000),零扩容、零浪费。
必要时主动调用 ensureCapacity
当集合已存在、但即将批量添加大量新元素(如读文件后 addAll()),可在添加前调用:
list.ensureCapacity(list.size() + expectedNewCount);
这样能确保下一次扩容直接跳到所需大小,避免分批添加时反复触发小步扩容。


















