String、StringBuffer、StringBuilder 是 Java 開發者每天都在使用的類別,但真正理解其底層機制、效能差異與適用場景的開發者並不多。本文從記憶體模型、JVM 編譯優化、實戰基準測試三個維度,徹底講透這三兄弟。
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 執行緒安全 | 天生安全 | 同步鎖保護 | 非安全 |
| 繼承關係 | final class | extends AbstractStringBuilder | extends AbstractStringBuilder |
| 引入版本 | JDK 1.0 | JDK 1.0 | JDK 1.5 |
| 底層儲存 | char[] / byte[] (JDK 9+) | char[] / byte[] | char[] / byte[] |
String s = "a";
s += "b"; // 實際發生:
// 1. new StringBuilder("a")
// 2. append("b")
// 3. toString() → 新 String 物件
// 4. 原 "a" 變成垃圾等待 GC
每次修改都產生新物件,頻繁操作會造成:
內部維護 char[] value(JDK 9+ 改為 byte[] + coder)與 int count:
append:確保容量 → 複製資料 → 更新 countnewCapacity = (oldCapacity + 1) * 2// 源碼
String s = "Hello" + name + "!";
// javac 編譯後 (javap -c)
new StringBuilder()
.append("Hello")
.append(name)
.append("!")
.toString()
編譯器自動插入 StringBuilder,**單行拼接完全無效能損失**。
String s = "A" + "B" + "C"; // 編譯期直接變 "ABC"
String s = "A" + "B" + var; // 編譯期合併常量:"AB" + var
// 源碼
String s = "";
for (int i=0; i<100; i++) s += i;
// 位元碼:迴圈內每次 new StringBuilder → 災難
Benchmark Mode Cnt Score Error Units StringConcat.singleStatement thrpt 10 12,450 ± 320 ops/ms StringConcat.loopPlus thrpt 10 320 ± 15 ops/ms StringConcat.loopStringBuilder thrpt 10 11,800 ± 280 ops/ms StringConcat.loopStringBuffer thrpt 10 9,200 ± 210 ops/ms
關鍵發現:
需要修改字串內容?
├─ 否 → 直接用 String(常量、參數傳遞、金鑰值)
└─ 是 → 多執行緒共享同一緩衝區?
├─ 是 → StringBuffer(極少見,通常用 ThreadLocal + StringBuilder)
└─ 否 → 單執行緒 / 區域變數 → StringBuilder
├─ 可預估最終長度 → new StringBuilder(estimatedCapacity)
├─ 大量單字元 append → sb.append('c') 而非 sb.append("c")
└─ 最終輸出前保持 StringBuilder 型態,避免過早 toString()
JDK 9 引入 byte[] value + byte coder 取代 char[]:
| 反模式 | 問題 | 修正 |
|---|---|---|
| String s = ""; for(...) s += x; | O(n²) 物件分配 | StringBuilder sb = new StringBuilder(); for(...) sb.append(x); |
| log.debug("msg=" + obj.toString()) | 日誌級別關閉時仍執行拼接 | log.debug("msg={}", obj) // SLF4J 延遲評估 |
| StringBuilder 作為欄位共享 | 執行緒不安全 | ThreadLocal<StringBuilder> 或方法區域變數 |
| new StringBuilder(1000000) | 過度預分配造成記憶體浪費 | 合理預估或分批處理 |
理解底層機制,才能寫出既優雅又高效的 Java 字串處理代碼。