字串拼接是 Java 高頻操作,但 String、StringBuilder、StringBuffer 三者效能差 10~100 倍、線程安全性截然不同。本文從 JVM 記憶體模型、源碼實現、基準測試三維度,給出毫無疑慮的選型標準。
| 維度 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 天然安全 (不可變) | 非安全 | 安全 (synchronized) |
| 底層結構 | final char[] / byte[] | char[] / byte[] (擴容) | char[] / byte[] (擴容) |
| 擴容策略 | 每次新建物件 | 1.5 倍 + 1 | 1.5 倍 + 1 |
| JDK 版本 | 1.0 | 5.0 (取代 StringBuffer) | 1.0 |
| 相對效能 (單執行緒) | 1x (基準) | ~50-100x | ~30-50x (同步開銷) |
String s = "a";
s += "b"; // 實際發生:
// 1. new StringBuilder()
// 2. append("a")
// 3. append("b")
// 4. toString() → 新 String 物件
// 5. 原 "a" 變垃圾
循環拼接 N 次 → 產生 N 個中間 StringBuilder + N 個 String,GC 壓力極大。
// StringBuffer (JDK 17)
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
// StringBuilder (JDK 17)
@Override
public StringBuilder append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuffer 每個方法加 synchronized,單執行緒下純粹增加鎖開銷 (CAS/偏向鎖優化後仍可測)。
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class StringConcatBenchmark {
@Benchmark
public void stringConcat() {
String s = "";
for (int i = 0; i < 100_000; i++) s += "x";
}
@Benchmark
public void builderConcat() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100_000; i++) sb.append("x");
}
@Benchmark
public void bufferConcat() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 100_000; i++) sb.append("x");
}
}
典型結果 (ms/op):
stringConcat:~12,000 µsbufferConcat:~180 µsbuilderConcat:~120 µsStringBuilder 比 StringBuffer 快 ~30%,比 String 快 ~100 倍。
需要拼接字串?
├─ 單執行緒 / 區域變數 / 高頻 → StringBuilder (首選)
├─ 多執行緒共享實例 → StringBuffer
├─ 少量拼接 / 常量 / 鍵值 → String (+ 足夠)
└─ 函數式風格 / Stream → Collectors.joining()
// 預估最終長度 ~2000 字元
StringBuilder sb = new StringBuilder(2048);
String result = list.stream()
.map(Object::toString)
.collect(Collectors.joining(", ")); // 內部用 StringBuilder
String s1 = "a" + "b"; // 編譯期常量折疊 → "ab" (池中)
String s2 = new String("ab"); // 堆中新物件
s2.intern(); // 手動入池,返回池中引用
StringBuilder:單執行緒、高效能、無同步開銷StringBuffer:舊版 API 相容、極少見String:可讀性優先、JIT 會優化