社交媒体

String 为什么不可变

String 为什么不可变

钩子:一段把程序拖慢的代码

先做个实验,循环拼接字符串一万次:

String s = "";
for (int i = 0; i < 10000; i++) {
    s = s + "a";       // 拼接
}

这段代码能跑,但和用 StringBuilder 的版本比,实测慢了近百倍(拼接次数越多差距越恐怖,10 万次时就是几百倍)。问题出在哪?

根源一句话:String 是不可变的(immutable)。

核心:String 不可变是什么意思

String s = "橙码";
s = s + "酱";     // 你以为 s "变了"?

你以为的:s 这个字符串长出了"酱"这个字。

实际发生的:

  1. "橙码" 这个对象从头到尾没变过;
  2. s + "酱" 创建了一个全新的字符串对象"橙码酱";
  3. s 只是改了指向(引用换了个对象),旧对象原地不动。

21-String拼接过程图.png

所以循环拼接一万次 = 创建一万个临时字符串对象,大部分转眼就成了垃圾。这就是慢 200 倍的账单。

解药:StringBuilder

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append("a");    // 始终在同一个对象里追加,不建新对象
}
String result = sb.toString();

StringBuilder 内部维护一个可扩容的字符数组,append 是往自己身上追加,对象还是那个对象。第 16 篇讲过的 return this 链式调用,就是它——现在你知道为什么它能一直 .append 下去了。

使用口诀:

  1. 循环里拼接字符串 → 必须用 StringBuilder(性能红线);
  2. 就拼两三下 → + 随便用,编译器会优化,可读性优先。

为什么要把 String 设计成不可变?

面试高频,两个理由够用:

  1. 安全:如果 String 可变,s 传给某个方法后被内部偷偷改了内容——文件路径、数据库连接串、用户名全都可能被篡改。不可变 = 传谁都不怕;
  2. 省内存 + 当锁用:字符串常量池(相同字面量共用一个对象)只在不可变的前提下成立;HashMap 的 key 大量用 String,key 若可变,存进去就找不到了——这条下下篇讲 HashMap 时会呼应。

本文示例代码均已本地实测通过,可直接运行。
对应文件:StringDemo.java


橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。