JDK 21 字符串拼接需按场景选择最优方案:静态拼接用+,编译器自动优化;单线程动态拼接优选StringBuilder;格式化模板结合formatted()与文本块;集合拼接用String.join()或Stream;多线程场景选StringBuffer。

字符串拼接不是“写完能跑”就结束的事——它直接牵动内存分配频率、GC 压力和 CPU 缓存效率。选错方式,10 万次拼接可能多占几十 MB 临时对象、触发数次 Full GC;选对方式,一次预分配 + 合理类型,性能差可达百倍。
明确场景再定方案:拼接次数、线程模型、内容来源
没有银弹,只有适配:
-
编译期已知的字面量拼接(如
"Hello" + " " + "World")→ JVM 自动优化为单个常量,无需干预 -
单线程循环拼接(100+次)→ 必用
StringBuilder,避免String +=产生的大量中间对象 -
多线程并发拼接同一逻辑串→ 优先考虑线程局部
StringBuilder(如ThreadLocal<StringBuilder>),比全局StringBuffer更轻量 -
拼接结果需长期缓存且内容重复率高(如 HTTP 头模板、SQL 片段)→ 可结合
String.intern()减少堆内重复字符串,但注意 JDK7+ 后 intern 存于堆中,非永久代
StringBuilder 预分配容量是性价比最高的优化动作
默认扩容策略(oldCap * 2 + 2)在不确定长度时易造成多次拷贝。实测显示:未预分配的 10 万次追加,平均触发 16 次数组复制;而预估长度后调用 new StringBuilder(estimatedSize),可将复制降为 0–1 次。
估算技巧:
- 固定前缀 + 动态数字 → 前缀长度 + 数字最大位数(如
"id:" + 99999→ 至少预留 3 + 5 = 8 字节) - JSON 或日志格式 → 按字段数 × 平均字段长 + 分隔符/引号开销粗略估算
- 不确定但有上限 → 直接按上限 + 10% 安全余量初始化
警惕隐式装箱与 toString() 带来的额外开销
常见低效写法:sb.append("value=").append(obj.getId()).append(", name=").append(obj.getName())
问题在于:obj.getId() 若返回 int,会自动装箱为 Integer,再调用其 toString();若返回 Long,还涉及更大对象创建。更优做法:
- 基础类型优先用
sb.append(int)、sb.append(long)等重载方法(直接写入数字字符,无对象生成) - 自定义对象提供
appendTo(StringBuilder)方法,避免临时toString()调用 - 避免在 append 中嵌套表达式,如
sb.append("x=" + computeX())→ 改为sb.append("x=").append(computeX())
对比验证:用真实数据代替经验判断
不同 JDK 版本、不同字符串长度、不同拼接模式下,最优解可能变化。建议在关键路径上做轻量基准测试:
- 用 JMH(Java Microbenchmark Harness)测试核心拼接逻辑,禁用 JIT 预热干扰
- 监控指标不止耗时:关注
GC count、allocated MB/sec、object allocation rate - 特别注意小字符串高频拼接场景(如日志 traceId 注入),此时
String.concat()在 JDK9+ 可能比StringBuilder更快(因避免对象创建开销)


















