核心差異速覽
| 特性 | String | StringBuffer | StringBuilder |
| 可變性 | 不可變 | 可變 | 可變 |
| 執行緒安全 | 是 (不可變天然安全) | 是 (方法同步) | 否 (非同步) |
| 效能 (單執行緒) | 最差 (頻繁建立新物件) | 中等 (同步開銷) | 最佳 (無同步) |
| 適用場景 | 常量、少量拼接、鍵值 | 多執行緒共享緩衝區 | 單執行緒大量拼接、建構器 |
原理解析
String:不可變的代價
String a = "a"; // 指向常量池 "a"
a = "b"; // 重新指向常量池 "b",原 "a" 仍存在 (若無引用則 GC)
String s = "ab";
s += "c"; // 實際執行:new StringBuilder().append(s).append("c").toString()
- 每次修改都產生新物件,舊物件等待 GC,記憶體壓力大
- 適合作為 Map Key、Set 元素、常量池複用
StringBuffer:同步的可變字串
- 內部維護
char[] value 陣列,預設容量 16,擴容策略 newCapacity = (oldCapacity << 1) + 2
- 所有公開方法加
synchronized,多執行緒安全但有鎖開銷
- JDK 1.0 產物,早期單執行緒也廣泛使用
StringBuilder:現代首選
- JDK 1.5 引入,API 與 StringBuffer 幾乎相同,但移除所有 synchronized
- 單執行緒下效能顯著優於 StringBuffer
- 非執行緒安全,多執行緒環境需自行同步或用 StringBuffer
效能實測代碼
public class StringPerfTest {
private static final int ITERATIONS = 50_000;
public static void main(String[] args) {
testStringConcat();
testStringBuffer();
testStringBuilder();
testLiteralConcat(); // 編譯期優化
}
static void testStringConcat() {
String s = "abcd";
long start = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) s += "add";
System.out.printf("String += : %,d ms%n", System.currentTimeMillis() - start);
}
static void testStringBuffer() {
StringBuffer sb = new StringBuffer("abcd");
long start = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) sb.append("add");
System.out.printf("StringBuffer.append: %,d ms%n", System.currentTimeMillis() - start);
}
static void testStringBuilder() {
StringBuilder sb = new StringBuilder("abcd");
long start = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) sb.append("add");
System.out.printf("StringBuilder.append: %,d ms%n", System.currentTimeMillis() - start);
}
static void testLiteralConcat() {
long start = System.currentTimeMillis();
for (int i = 0; i < ITERATIONS; i++) {
String s = "abcd" + "abcd" + "abcd"; // 編譯期常量折疊,極快
}
System.out.printf("Literal concat: %,d ms%n", System.currentTimeMillis() - start);
}
}
實測結果分析 (典型輸出)
String += : 1,250 ms
StringBuffer.append: 2 ms
StringBuilder.append: 1 ms
Literal concat: 0 ms

- String +=:O(n²) 級別,隨迭代增長呈指數級變慢,嚴禁在迴圈中使用
- StringBuffer/StringBuilder:O(n) 線性,差異僅在同步開銷 (約 1-2 倍)
- 字面量拼接:編譯期常量折疊 (
Constant Folding),直接生成單一常量,零開銷
- 不同環境 (IDE/命令列/JIT 預熱) 絕對值差異大,相對比例穩定
最佳實踐指南
選型決策樹
- 編譯期常量拼接 → 直接用
+ (編譯器優化)
- 單執行緒、方法區域變數拼接 → StringBuilder
- 多執行緒共享緩衝區 → StringBuffer
- 最終結果需不可變、作為 Key/常量 → String (由 Builder
toString() 產生)
常見反模式修正
| 反模式 | 問題 | 修正 |
for(...) s += x; | O(n²) 物件建立 | StringBuilder sb = new StringBuilder(); for(...) sb.append(x); return sb.toString(); |
StringBuffer 作為區域變數 | 無謂同步開銷 | 改用 StringBuilder |
String.format() 在熱路徑 | 解析格式字串開銷大 | 改用 StringBuilder 或 MessageFormat 緩存 |
日誌 log.debug("msg" + obj) | 即使不輸出也執行拼接 | if (log.isDebugEnabled()) log.debug("msg" + obj); 或用 SLF4J 佔位符 log.debug("msg {}", obj) |
進階補充
- Java 9+ Compact Strings:Latin-1 字串採用
byte[] 節省 50% 記憶體
- StringJoiner / String.join():Java 8 新增,適合分隔符拼接
- Text Blocks (Java 15+):多行字串字面量,免去換行轉義
- 逃逸分析:JIT 可將未逃逸的 StringBuilder 優化為標量替換,進一步消除分配