String 是 Java 使用最頻繁的類,但其不可變特性帶來的性能陷阱常被忽視。本文深入剖析 substring 內存洩漏、+ 拼接編譯器優化邊界、StringBuilder vs StringBuffer 選型、split vs StringTokenizer 分割性能四大核心主題,提供生產級最佳實踐。
JDK 6/7 中 String 內部包含 char[] value、int offset、int count。substring 通過共享原 value 數組、調整 offset/count 實現 O(1) 截取:
public String substring(int beginIndex, int endIndex) {
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
洩漏場景:從 100MB 日志字符串截取 10 字符的錯誤碼,原 100MB 數組因被子串引用而無法 GC,造成嚴重內存佔用。
String s = "ABC" + 200 + 'D';
// 編譯器優化為:
String s = new StringBuilder().append("ABC").append(200).append('D').toString();
多個常量拼接直接折疊:"Hello" + " " + "World" → "Hello World"。
鐵律
單行可見的拼接用 + 可讀性最高;循環、條件分支、多語句拼接必須顯式使用 StringBuilder。
三、StringBuilder vs StringBuffer:性能與線程安全的權衡
| 維度 | StringBuilder | StringBuffer |
|---|---|---|
| 線程安全 | 否 | 是(所有 public 方法 synchronized) |
| 單線程性能 | 基準(快 ~2 倍) | 慢(鎖開銷) |
| 多線程性能 | 不適用(需外部同步) | 極差(鎖爭用導致線程頻繁切換,吞吐甚至低於單線程) |
實測數據(100 萬次 append):
StringBuffer: 33,979,818 ns StringBuilder: 14,061,978 ns
結論:單線程/線程封閉場景絕對優先 StringBuilder。多線程共享字符串構建極罕見,若真有需求,考慮 ThreadLocal、無鎖隊列 或分片聚合,避免全局鎖瓶頸。
// 測試數據:100 萬個數字用逗號分隔
String str = IntStream.range(0, 1_000_000).mapToObj(String::valueOf).collect(Collectors.joining(","));
// split 返回數組
long t1 = System.nanoTime();
String[] arr = str.split(",");
System.out.println("split: " + (System.nanoTime() - t1)); // ~2.5 億 ns
// StringTokenizer 遍歷
t1 = System.nanoTime();
StringTokenizer st = new StringTokenizer(str, ",");
while (st.hasMoreTokens()) st.nextToken();
System.out.println("tokenizer: " + (System.nanoTime() - t1)); // ~5,300 萬 ns (快 4.7 倍)
// 若 StringTokenizer 也需存入 List/數組
List list = new ArrayList<>();
while (st.hasMoreTokens()) list.add(st.nextToken());
// tokenizer 時間上升至 ~3 億 ns,反超 split
split 內部編譯正則、創建數組、填充數組,一次性完成。StringTokenizer 僅遍歷不分配容器,極快;但若需收集結果,List.add/數組賦值開銷抵消優勢。split(代碼簡潔、性能穩定);僅遍歷處理用 StringTokenizer 或手寫索引解析;高性能場景用專用解析庫(如 Jackson CSV、Apache Commons CSV)。| 場景 | 推薦做法 | 避坑關鍵 |
|---|---|---|
| 長期持有大字符串子串 | new String(big.substring(...)) | JDK 8+ 無憂 |
| 單行多項拼接 | + 操作符 | 編譯器自動優化 |
| 循環/複雜邏輯拼接 | StringBuilder | 避免隱式創建構建器 |
| 多線程共享構建 | 分片/無鎖/ThreadLocal | 別用 StringBuffer 全局鎖 |
| 分割需數組/列表 | split(regex) | 預編譯 Pattern 複用 |
| 分割僅遍歷 | StringTokenizer/手寫 | 避免二次收集 |