应优先使用预设容量的 stringbuilder 进行字符串拼接,避免隐式拼接与装箱,多线程共享时才用 stringbuffer,超大数据流采用流式处理而非全量加载。

大量字符串拼接引发内存抖动,本质是频繁创建和丢弃短生命周期的 String 对象,导致年轻代 GC 压力陡增、对象分配速率飙升、Eden 区快速填满,进而触发高频 minor GC,甚至引发内存碎片化。解决关键不是“少拼”,而是“不反复新建不可变对象”。
优先用 StringBuilder(单线程场景)
StringBuilder 是可变字符序列,复用同一块 char[] 数组,避免每次拼接都生成新对象。
- 循环外创建实例,循环内只调用 append() —— 切忌在循环里 new StringBuilder()
- 初始化时预设容量:比如拼接 1000 条平均长度 20 的字符串 + 分隔符,总长预估约 25000,就写 new StringBuilder(25000);不确定时也建议设为 1024 或 4096,远优于默认 16
- 拼完立刻调用 toString() 获取结果,之后可清空复用(sb.setLength(0)),避免反复 new
多线程共享拼接时选 StringBuffer
仅当多个线程共用同一个拼接实例(如全局缓存构建器)才需考虑 StringBuffer。
- 它所有 public 方法加了 synchronized,保证线程安全但有锁开销
- 单线程下比 StringBuilder 慢 10%~15%,不要“图安心”误用
- 更推荐做法:用局部 StringBuilder + 外层同步控制(如 synchronized 块),兼顾安全与性能
避开隐式拼接和装箱陷阱
有些写法看似简单,实则每执行一次就触发多次对象创建。
- 日志中避免 "msg=" + value + ", code=" + code —— 即使日志关闭,拼接仍执行,还触发 String.valueOf(int/boolean) 装箱
- 改用 SLF4J 占位符:log.info("msg={}, code={}", value, code),参数只在日志启用时求值
- 避免在高频率路径(如循环体、热点方法)中使用 String.format() 或 String.concat(),它们内部仍依赖 StringBuilder 且无容量预设
超大数据流要跳出来想
如果待拼接内容来自文件、数据库游标或网络响应,总量远超堆内存,就别想着“全读进内存再拼”。
- 采用流式处理:逐行读取 → 直接写入 FileWriter / ByteArrayOutputStream / HttpServletResponse.getOutputStream()
- 配合 try-with-resources 确保缓冲区和流及时释放
- 必要时启用 G1GC 并调优:-XX:+UseG1GC -XX:MaxGCPauseMillis=200,但这是兜底手段,不能替代代码层面规避对象爆炸
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











