
字符串不可变性如何让 + 连接在循环中变慢
Java 中 String 是不可变的,每次用 + 拼接都会新建一个对象。在 for 循环里反复拼接,比如 str += "a",实际会生成大量中间 String 对象,触发频繁 GC,性能断崖式下降。
- 编译器对静态字符串(如
"a" + "b" + "c")会优化为常量池中的单个字面量,不产生运行时开销 - 但含变量的拼接(如
s + "x")会被编译为new StringBuilder().append(s).append("x").toString()—— 每次都新建StringBuilder - 循环中用
+=等价于每次新建StringBuilder→append→toString(),O(n²) 时间复杂度
什么时候该用 StringBuilder 而不是 +
判断依据不是“字符串多长”,而是“拼接动作是否发生在运行时循环或多次分支中”。即使只拼 3 个变量,只要逻辑路径导致拼接被重复执行,就该换 StringBuilder。
- 循环内拼接:必须用
StringBuilder,且建议预设容量(new StringBuilder(1024)),避免内部数组扩容 - 方法参数拼接后仅返回一次:如
return prefix + value + suffix,JVM 通常已优化为StringBuilder,可不改 - 日志拼接(如
log.debug("id=" + id + ", name=" + name)):若日志未开启,字符串仍会构造 —— 应改用占位符(log.debug("id={}, name={}", id, name))或延迟计算
String.concat() 和 + 的底层差异
String.concat() 是 String 类原生方法,直接操作字符数组,不经过 StringBuilder;而 + 编译后几乎总是走 StringBuilder 路径(Java 9+ 对两字符串拼接有少量优化,但仍非直接数组拷贝)。
-
"a".concat("b"):分配一个新数组,复制两次源数组内容,无额外对象开销 -
"a" + "b":编译期优化为字面量,无运行时行为;但s1 + s2(变量)编译为new StringBuilder().append(s1).append(s2).toString() -
concat()只支持单个字符串参数,无法链式调用,灵活性差,日常少用
Java 9+ 的 String 内部表示变化对连接的影响
Java 9 把 String 的底层从 char[] 改为 byte[] + coder(标识 Latin-1 或 UTF-16),节省内存。但这不影响拼接逻辑,只影响 concat() 和 StringBuilder.toString() 的内部拷贝效率。
立即学习“Java免费学习笔记(深入)”;
不可变性是设计选择,不是性能缺陷;问题出在误把“语法糖”当“零成本抽象”。真正要盯住的是运行时拼接频次和上下文,而不是某个连接符号本身。










