2026-02

Java 字符串拼接性能笔记

深入理解 StringBuffer、+ 运算符、formatted() 的底层原理与性能对比

Java 性能优化 最佳实践

目录导航

一、三种拼接方式及其底层原理

1 `+` 运算符拼接
底层实现(不同 Java 版本)

Java 8 及之前:

编译器会将 +转换成 StringBuilderappend(),然后调用 toString()

// 例如:
"a" + "b" + "c"  
// → 编译后等价于:
new StringBuilder().append("a").append("b").append("c").toString()

在循环中使用 +(如 fors += i):

每次循环都会新建一个 StringBuilder,产生大量临时对象,性能极差。

Java 9+:

引入了 invokedynamic+ StringConcatFactory,不再机械生成 StringBuilder,而是动态生成更高效的拼接指令(直接操作字符数组)。单行 +的性能已非常接近手写的 StringBuilder

2 `StringBuffer`
特点
  • 线程安全:所有公共方法(appendinsert等)都用 synchronized修饰。
  • 内部维护一个可变的 char[]/ byte[],支持动态扩容。
性能真相
  • 因为需要获取和释放锁,单线程下 StringBufferStringBuilder
  • 多线程下也很少需要共享一个 StringBuffer做拼接(字符串拼接通常是线程局部的)。
  • 结论:StringBuffer不是最快的,而是最慢的可变字符串拼接方式(相对于 StringBuilder)。
  • 误解来源:早期 JDK(1.5 之前)没有 StringBuilder,它是当时唯一的高效可变字符串类。
3 `formatted()` / `String.format()`
底层实现
  • 内部使用 java.util.Formatter,会解析格式说明符(如 %s%d)。
  • 每次调用都会新建 Formatter对象,可能涉及正则、类型转换、Locale 处理。
  • 内部还会分配临时缓冲区(StringBuilderchar[])。
性能定位
  • 最慢:解析格式串 + 对象创建 + 类型转换的开销远大于简单拼接。
  • 适用于需要格式化(对齐、数字格式、日期)的场景,不是为了极致性能设计。

二、性能对比总结(单线程,大量拼接)

方式 性能排名 说明
StringBuilder 最快 无锁,可变,可复用
+(单行/常量折叠) 很快 Java 9+ 已高度优化,接近 StringBuilder
+(循环内拼接) 很慢 每次循环新建 StringBuilder
StringBuffer 较慢 同步开销,不推荐单线程使用
formatted()/ String.format() 最慢 功能强大但开销大
注意:如果拼接次数很少(< 5 次),所有方式性能差异可忽略,优先选择可读性最好的写法。

三、最佳实践建议

场景 推荐写法
单行简单拼接(已知固定字符串) 直接用 +,代码最清晰
循环内拼接 / 动态大量拼接 手动创建 StringBuilder并复用
多线程环境 依然使用局部 StringBuilder,最终得到不可变 String再共享;无需 StringBuffer
需要格式化输出(数字、日期、对齐) 使用 String.formatformatted(),避免在高频循环中调用

四、典型错误与正确示例

典型错误示例
// 错误:循环内用 + 拼接
String s = "";
for (int i = 0; i < 10000; i++) {
    s += i;   // 每次循环新建 StringBuilder
}
正确示例
// 正确:循环内复用 StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String s = sb.toString();
总结:
  • 性能上:StringBuilder> +(单行)> +(循环)≈ StringBuffer> formatted()
  • 选择原则:优先保证代码可读,在性能瓶颈处改用 StringBuilder,格式化需求用 format系列方法。
  • 不要迷信 StringBuffer,它在现代 Java 中已经不是性能选项。