字符串是 Java 應用中最高頻對象,理解 String、StringBuilder、StringBuffer 三者差異,對寫出高性能、線程安全代碼至關重要。本文從內存模型、并发語義、JVM 優化、實戰選型四個維度徹底講透。
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 天然安全(不可變) | 非線程安全 | 同步方法,線程安全 |
| 性能 | 拼接產生大量中間對象 | 單線程最高 | 同步開銷略低於 Builder |
| 適用場景 | 常量、鍵、少量拼接 | 單線程大量拼接/構建 | 多線程共享緩衝區 |
| 繼承關係 | final class | extends AbstractStringBuilder | extends AbstractStringBuilder |
hashCode 首次計算後緩存,Map/Set 查找 O(1)。// 源碼
String s = "a" + "b" + "c"; // 編譯期常量折疊 → "abc"
String s2 = "a";
s2 += "b"; // 運行期:new StringBuilder().append("a").append("b").toString()
StringBuilder(Java 9+ 可能用 invokedynamic + StringConcatFactory)。StringBuilder sb = new StringBuilder(256); // 預估容量避免擴容
for (int i = 0; i < 10000; i++) sb.append(i).append(',');
String result = sb.toString(); // 僅一次字符數組拷貝
char[]/byte[](Java 9+ Compact Strings),動態擴容 1.5 倍。StringBuffer 快 10-15%。append 導致數組越界、數據錯位、size 計數錯誤。public synchronized StringBuffer append(String str) { ... }
synchronized,適合多線程共享緩衝區(如日誌收集器)。ThreadLocal、StringJoiner(Java 8)、無鎖隊列 + 單線程消費。| 場景 | 推薦 | 理由 |
|---|---|---|
| 方法內部臨時拼接 | + 操作符 / StringBuilder | 編譯器優化 / 手動控制 |
| 循環/大量拼接 | StringBuilder | 避免 O(N²) 對象創建 |
| 多線程共享構建器 | StringBuffer / ThreadLocal | 同步 / 隔離 |
| 常量、Map Key、Switch | String | 不可變、Hash 緩存、池化 |
| 格式化輸出 | String.format / StringJoiner | 可讀性、分隔符處理 |
| 高性能日誌 | StringBuilder + 異步刷盤 | 低延遲、減少 GC |
byte[] + coder,Latin-1 字符佔 1 字節,內存降低 ~50%。invokedynamic 根據參數類型動態選擇最優拼接策略(數組拷貝、Builder、MH),無需手動優化。String 不可變是基石,利用常量池與 Hash 緩存;StringBuilder 是單線程構建利器,預估容量、鏈式調用;StringBuffer 逐漸退出歷史舞台,現代并发編程有更優雅的隔離方案。掌握底層機制,拒絕盲目拼接,代碼既快又穩。