字符串拼接应优先用StringBuilder(单线程)或StringBuffer(多线程),避免+操作引发OOM;需预设容量、复用实例、分批处理大数据流,并警惕隐式拼接陷阱。

大量字符串拼接引发堆内存溢出,核心问题在于 String 的不可变性 和 频繁创建临时对象。每次用 + 或 += 拼接,都会生成新 String 对象,旧对象留在堆中等待 GC,数据量大时极易触发 OOM。
优先使用 StringBuilder(单线程场景)
StringBuilder 是可变字符序列,内部基于 char[] 数组扩容,避免重复创建对象。
- 初始化时指定合理容量(如已知最终长度的 1.2 倍),减少数组扩容次数
- 避免在循环内反复新建 StringBuilder 实例;应在循环外创建并复用
- 示例:错误写法:
for (String s : list) { result += s; }→ 多次生成 String
推荐写法:StringBuilder sb = new StringBuilder(estimatedCapacity); for (String s : list) sb.append(s); return sb.toString();
多线程环境下选用 StringBuffer
StringBuffer 与 StringBuilder 接口一致,但所有方法加了 synchronized,适合并发拼接场景。
- 性能略低于 StringBuilder,但保证线程安全
- 若拼接逻辑本身已在线程安全上下文中(如锁保护块内),仍可优先用 StringBuilder 提升吞吐
- 不建议用
String.concat()或String.join()处理超大数据集——它们底层仍会创建中间数组或 StringBuilder,且无容量预设能力
超大数据流拼接:分批 + 流式处理
当待拼接字符串来自文件、数据库游标或网络流,总量远超堆内存时,需跳出“全量加载再拼接”思路。
- 逐行/逐批次读取 + 写入目标缓冲区(如 ByteArrayOutputStream、FileWriter 或响应输出流)
- 配合 try-with-resources 确保资源及时释放
- 必要时启用 JVM 参数优化 GC 行为,例如
-XX:+UseG1GC -XX:MaxGCPauseMillis=200,但这是辅助手段,不能替代代码优化
警惕隐式字符串拼接陷阱
以下写法看似简洁,实则暗藏高开销:
-
log.info("user=" + user.getId() + ", action=" + action + ", time=" + System.currentTimeMillis());→ 即使日志级别关闭,拼接仍执行 - 改用占位符(如 SLF4J 的
log.info("user={}, action={}, time={}", user.getId(), action, System.currentTimeMillis());),仅当日志启用时才执行参数计算和拼接 - JDK 13+ 可考虑使用
String.formatted()替代String.format()(后者创建额外对象),但仍需注意预估长度避免内部 StringBuilder 频繁扩容

















