單線程高頻修改:
StringBuilder>StringBuffer>String
多線程共享:StringBuffer>StringBuilder(需外部同步)
常量拼接/少量操作:String編譯器優化極快
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 天然安全(不可變) | 同步方法,線程安全 | 非同步,非線程安全 |
| 底層結構 | final char[] / byte[] | 可擴容 char[] / byte[] | 可擴容 char[] / byte[] |
| 拼接操作 | 產生新對象 | 原地修改 | 原地修改 |
| JDK 版本 | 1.0 | 1.0 | 5.0 |
| 性能排序 | 最低 (頻繁修改時) | 中等 (同步開銷) | 最高 (無同步) |
String s = "abc";
for (int i = 0; i < 10000; i++) {
s += "def"; // 每次循環:新建 StringBuilder → append → toString → 新 String 對象
s = "abc"; // 10000 次循環 ≈ 20000+ 臨時對象 → GC 壓力大
}
內存震盪:大量短命對象觸發 Minor GC,甚至 Full GC,嚴重影響吞吐量。
// 源碼
String s = "This is only a" + " simple" + " test";
// 編譯器優化後 (javap -c 可見)
String s = "This is only a simple test"; // 編譯期常量折疊,零運行開銷
但變量拼接無優化:
String a = "This is only a";
String b = " simple";
String c = " test";
String s = a + b + c; // 運行期 StringBuilder 拼接,有開銷
public synchronized void append(String str) {
toStringCache = null;
super.append(str);
}
每次 append 加鎖,單線程下純粹開銷。JDK 5 引入 StringBuilder 專門解決此痛點。
public class ConcatBenchmark {
static final int LOOPS = 100_000;
public static void main(String[] args) {
// String +=
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < LOOPS; i++) s += "x";
System.out.println("String += : " + (System.currentTimeMillis() - t1) + "ms");
// StringBuffer
long t2 = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < LOOPS; i++) sb.append("x");
System.out.println("StringBuffer: " + (System.currentTimeMillis() - t2) + "ms");
// StringBuilder
long t3 = System.currentTimeMillis();
StringBuilder sbl = new StringBuilder();
for (int i = 0; i < LOOPS; i++) sbl.append("x");
System.out.println("StringBuilder: " + (System.currentTimeMillis() - t3) + "ms");
}
}
// 典型輸出 (單線程):
// String += : 4500ms+
// StringBuffer: 15ms
// StringBuilder: 8ms
// 方案一:雙指針遍歷 String (不創建新對象)
public boolean isPalindrome(String s) {
int l = 0, r = s.length() - 1;
while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
return true;
}
// 方案二:StringBuilder 反轉比較 (代碼極簡)
public boolean isPalindromeSB(String s) {
return s.equals(new StringBuilder(s).reverse().toString());
}
// 方案三:過濾非字母數字後判斷
public boolean isPalindromeFiltered(String s) {
StringBuilder filtered = new StringBuilder();
for (char c : s.toCharArray())
if (Character.isLetterOrDigit(c)) filtered.append(c);
String clean = filtered.toString();
return clean.equals(new StringBuilder(clean).reverse().toString());
}
需要頻繁修改字符串?
├── 多線程共享實例? ──▶ StringBuffer
└── 單線程/線程封閉? ──▶ StringBuilder (首選)
只有少量拼接/常量拼接? ──▶ String (編譯器優化)
需不可變/作為 Map Key? ──▶ String
StringBuilder 是 JDK 5 為單線程高性能場景定制的「去同步版 StringBuffer」。現代開發中,默認選 StringBuilder,僅在多線程直接共享緩衝區時用 StringBuffer,利用編譯器常量折疊優化靜態拼接。