:避免内存复制的最优实践
" />
本文介绍在 java 中将 `list
在高吞吐、低延迟的服务场景中,对嵌套列表(如 List
✅ 最优解:预计算总大小 + 批量添加
核心思想是用一次遍历估算最终容量,再用一次遍历填充数据,全程规避扩容:
// 第一步:精确计算所有 childList 的总元素数(O(n) 时间,假设 size() 是 O(1))
int expectedSize = parentList.stream()
.mapToInt(obj -> obj.getChildList().size())
.sum();
// 第二步:初始化具有精确容量的 ArrayList(避免任何内部扩容)
List result = new ArrayList<>(expectedSize);
// 第三步:逐个添加子列表(addAll 内部使用 System.arraycopy,高效且无扩容)
for (Object obj : parentList) {
result.addAll(obj.getChildList());
} ? 为什么这更高效?
- ArrayList(int initialCapacity) 构造器直接分配足够空间,后续 addAll() 仅执行内存块拷贝(System.arraycopy),时间复杂度为 O(k),k 为待添加元素数;
- 相比之下,未指定容量的 new ArrayList() 默认容量为 10,若总元素为 100,000,则需约 17 次扩容(每次扩容约 1.5 倍),累计复制元素超 200,000 次;
- Stream.flatMap 虽函数式优雅,但 Collectors.toList() 返回的 ArrayList 无法预设容量(JDK 当前实现不支持),且流式处理本身有额外装箱/迭代器开销。
⚠️ 注意事项与前提条件
- ✅ 前提:childList.size() 必须是 O(1) 操作(如 ArrayList、LinkedList 均满足;若为自定义慢速 size() 实现则不适用);
- ✅ 推荐:若 parentList 本身很大(如 > 10,000),可将 stream().mapToInt(...).sum() 替换为传统 for 循环,避免 Stream 创建开销;
- ⚠️ 避免:不要使用 Collections.addAll(result, ...),它不接受 Collection 参数,且无法批量插入 List;
- ? 扩展:若需不可变结果,可在构建完成后调用 Collections.unmodifiableList(result),但切勿在构建过程中包装——会破坏性能。
总结:预分配容量是 Java 集合扁平化的黄金准则。相比函数式写法,显式控制容量的双遍历方案在真实业务负载下通常可降低 30%~70% 的 CPU 占用,尤其适用于服务端高频调用路径。务必在性能敏感代码中优先考虑容量预估,而非依赖“自动”集合行为。










