字符串處理三巨頭是 Java 基礎必考點。本文從底層實現、線程安全模型、JDK 版本優化、實測性能、編譯器語法糖五個維度深度對比,給出生產環境選型決策樹。
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 天然安全 (不可變) | 同步方法,安全 | 非同步,不安全 |
| 繼承關係 | final class | extends AbstractStringBuilder | extends AbstractStringBuilder |
| JDK 引入 | 1.0 | 1.0 | 5.0 |
| 底層存儲 | byte[] (JDK 9+) / char[] | byte[] (JDK 9+) / char[] | byte[] (JDK 9+) / char[] |
| 擴容策略 | N/A | 2倍 + 2 | 2倍 + 2 |
| 主要用途 | 常量、Key、少量拼接 | 多線程共享緩衝區 | 單線程大量拼接 |
abstract class AbstractStringBuilder implements Appendable, CharSequence {
byte[] value; // JDK 9+ 緊湊字符串
byte coder; // LATIN1 或 UTF16
int count; // 字符數量
// 核心擴容邏輯
private void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity;
if (newCapacity < 0) newCapacity = Integer.MAX_VALUE;
value = Arrays.copyOf(value, newCapacity);
}
// append 核心:確保容量 → 編碼寫入 → count++
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
putStringAt(count, str, coder);
count += len;
return this;
}
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null; // 緩存失效
super.append(str);
return this;
}
@Override
public synchronized int length() { return count; } // 連讀取都加鎖!
每個公共方法都加 synchronized,即便是 length()、charAt() 也加鎖,高併發下鎖爭用嚴重。
// 無 synchronized 修飾
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
繼承父類所有功能,但完全移除同步,單線程下性能接近 C 級別。
substring 複製數組,修復內存洩漏byte[] + coder 替代 char[],Latin-1 字符節省 50% 內存String.repeat()、strip() 等新 APIStringBuilder 內部優化 putStringAt 向量化// 源碼
String s = "a" + "b" + c + "d";
// 反編譯 (JDK 8-)
String s = new StringBuilder().append("a").append("b").append(c).append("d").toString();
// 反編譯 (JDK 9+ invokedynamic)
String s = StringConcatFactory.makeConcatWithConstants(...); // 調用點綁定,更靈活
"a" + "b" 編譯期直接合併為 "ab"s += "x" 每次迭代創建新 StringBuilder → 性能災難| 場景 | String + | StringBuffer | StringBuilder |
|---|---|---|---|
| 單線程循環拼接 | ~12,000 ms | ~8 ms | ~5 ms |
| 多線程共享拼接 (10線程) | N/A | ~45 ms | 數據錯誤/崩潰 |
| 預分配容量 (100萬) | N/A | ~4 ms | ~3 ms |
需要拼接字符串?
├─ 少量、常量、非熱點 → 直接用 + (可讀性最高,編譯器優化)
├─ 大量拼接 / 熱點路徑?
│ ├─ 單線程 / 局部變量 / 線程封閉 → StringBuilder (性能王者)
│ │ └─ 能預估大致長度? → new StringBuilder(estimatedSize) (避免擴容)
│ └─ 多線程共享同一緩衝區 → StringBuffer (或 ThreadLocal + 合併)
└─ 需要不可變、作為 Key、常量池複用 → String
java.io.StringWriter:實現 Writer 接口,適配流式 APIcom.google.common.base.Joiner (Guava):Joiner.on(",").skipNulls().join(list)String.join() (JDK 8+):String.join(",", "a", "b", "c")Collectors.joining() (Stream):list.stream().collect(Collectors.joining(","))StringBuffer sb = new StringBuffer(); sb.append(null); 存入什麼?"null" 字符串 (非 null 值)StringBuilder 線程不安全表現?數據丟失、數組越界、count 錯誤、死循環StringBuilder 可繼承嗎?非 final,但不建議繼承 (內部無防禦性拷貝)