字串操作是 Java 開發最高頻場景之一。String、StringBuffer、StringBuilder 三者關係緊密卻差異巨大。本文從源碼層面剖析三者異同,助你在面試與實戰中精準選型。
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 是(不可變天然安全) | 是(synchronized) | 否 |
| 繼承結構 | final class | extends AbstractStringBuilder | extends AbstractStringBuilder |
| JDK 版本 | 1.0 | 1.0 | 1.5 |
| 效能(單執行緒) | 最差 | 中等 | 最佳 |
public final class String implements Serializable, Comparable<String>, CharSequence {
// JDK 9+ 使用 byte[] + coder,節省空間
private final byte[] value;
private final byte coder; // 0=LATIN1, 1=UTF16
// 所有「修改」操作都會返回新 String 物件
public String concat(String str) {
int len = str.length();
if (len == 0) return this;
byte[] v = Arrays.copyOf(value, value.length + len);
str.getBytes(v, value.length);
return new String(v, coder);
}
}
每次拼接、替換、子字串都會分配新記憶體、複製陣列。循環拼接會產生大量臨時物件,觸發頻繁 GC。
abstract class AbstractStringBuilder implements Appendable, CharSequence {
byte[] value; // 非 final,可擴容
int count; // 實際長度
// 核心擴容邏輯
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) expandCapacity(minimumCapacity);
}
void expandCapacity(int minimumCapacity) {
int newCapacity = value.length * 2 + 2;
if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minimumCapacity);
value = Arrays.copyOf(value, newCapacity);
}
}
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
public synchronized int indexOf(String str, int fromIndex) {
return super.indexOf(str, fromIndex);
}
所有公共方法加 synchronized,多執行緒安全但效能損耗大。
public StringBuilder append(String str) {
super.append(str);
return this;
}
JDK 5 引入,專為單執行緒場景設計。現代 JVM 對無鎖代碼優化極好(逃逸分析、鎖消除)。
// String: ~4500ms
String s = "";
for (int i = 0; i < 1_000_000; i++) s += "a";
// StringBuffer: ~15ms
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 1_000_000; i++) sb.append("a");
// StringBuilder: ~10ms
StringBuilder sbl = new StringBuilder();
for (int i = 0; i < 1_000_000; i++) sbl.append("a");
結論:單執行緒首選 StringBuilder;多執行緒共享緩衝區用 StringBuffer;少量拼接或常量用 String。
錯誤。編譯期常量折疊會直接優化為 "abc",零開銷。
視情況。JDK 6+ 引入鎖消除、偏向鎖,單執行緒下 StringBuffer 經 JIT 優化後差距極小。但 StringBuilder 仍是標準選擇。
+,編譯器自動優化為 StringBuilder。new StringBuilder(),預估容量 new StringBuilder(1024) 避免擴容。StringBuffer 或 ThreadLocal<StringBuilder>。log.debug("user={}, id={}", name, id) 避免不必要拼接。JDK 9 將 char[] 改為 byte[] + coder,純 ASCII 字串記憶體減半。三大類同步受益,無需代碼修改。
理解三者底層實現差異,核心在於可變性與同步策略。實戰中:單執行緒用 StringBuilder,多執行緒共享用 StringBuffer,其餘用 String。配合 JDK 9+ Compact Strings,字串處理效能與記憶體表現雙贏。