Java中+拼接字符串仅在编译期确定所有操作数时(如"a"+"b")被优化为常量;含变量、循环或方法调用时,每次均新建StringBuilder对象,导致GC压力上升。

加号拼接在什么情况下会变慢
Java里用+拼字符串,编译器会自动优化成StringBuilder,但**只限于编译期能确定所有操作数的场景**。比如"a" + "b" + "c"会被直接编译成"abc";而一旦涉及变量、循环或方法调用,就真会每次新建StringBuilder对象——不是复用,是反复构造+toString。
- 循环中写
str += "x":每次迭代都新建一个StringBuilder,再转成String,GC压力明显上升 - 方法参数拼接如
log("id=" + id + ", name=" + name):如果id和name是运行时值,这里就是三次对象创建(两个临时String+ 一次StringBuilder) - 字符串常量混变量:
"prefix" + someVar + "suffix"→ 编译后仍是new StringBuilder().append(...).toString(),但只建一次StringBuilder,比循环好,不如手动复用
StringBuilder怎么用才不白费力气
手动用StringBuilder的关键是「避免无谓重建」和「预估容量」。默认构造函数开销小,但扩容会触发数组复制;初始容量设太小,频繁扩容;设太大,浪费内存。
- 知道大概长度时,优先用
new StringBuilder(estimatedLength),比如拼10个平均8字符的字段,预设128就够了 - 链式调用没问题:
sb.append(a).append(b).append(c),返回的是同一个实例,不用反复赋值 - 别在循环外声明
StringBuilder然后在多个无关逻辑里复用——容易串数据,尤其多线程下没加锁就是bug - 拼完必须调
toString(),直接打印sb会输出StringBuilder@xxx,不是内容
String.concat()和String.join()适合谁
String.concat()只接受单个String参数,本质是new String(value) + str的底层实现,性能和+差不多,没优势;String.join()则专为“用分隔符拼集合”设计,内部用了StringBuilder并预估总长,比手写循环++=稳得多。
-
String.join("-", list)比list.stream().collect(Collectors.joining("-"))更轻量,没Stream开销 -
String.join()对null元素抛NullPointerException,不是静默跳过 - 只有两个字符串拼接?
a + b和a.concat(b)性能几乎一样,选可读性高的就行
IDEA或JProfiler里怎么确认拼接是否成了瓶颈
光看代码猜没用,得看实际对象分配。在JVM启动参数加-XX:+PrintGCDetails,观察StringBuilder和String的创建频率;或者用JProfiler的Allocation Hot Spots,过滤java.lang.StringBuilder,看是不是集中在某个循环或日志方法里。
立即学习“Java免费学习笔记(深入)”;
- 注意:Lambda里用
+拼接(如map(x -> "id:" + x.id)),每个lambda执行都会新建StringBuilder,容易被忽略 - 日志框架如SLF4J的占位符写法
log.info("user {}, status {}", id, status),底层用Object[]延迟格式化,完全避开字符串拼接,比任何StringBuilder都干净 - Android开发慎用
StringBuilder在主线程大量拼接,可能触发卡顿,优先考虑CharSequence或预生成文本
真正影响性能的从来不是“该不该用StringBuilder”,而是“在哪初始化、容量设多少、作用域划到哪”。很多人改了+=为StringBuilder却还在循环里new它,等于换汤不换药。










