字符串是程序開發中使用頻率最高、操作最複雜的數據類型之一。從 C/C++ 的字符數組操作到 Java/.NET 的專門優化,字符串的重要性不言而喻。日志系統每天產生的海量字符串對象,若不加特別設計,極易引發內存壓力與性能瓶頸。本文將系統梳理 Java 中 String 的不可變機制、常量池原理、比較陷阱,以及 StringBuffer/StringBuilder 的可變設計與性能優勢。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; // JDK 8 及之前版本
// private final byte[] value; // JDK 9+ 引入 Compact Strings
// ...
}
final 修飾意味著:一旦創建,字符數組引用不可變,內容不可變。任何「修改」操作(拼接、替換、截取)實際上都是創建新 String 對象。
String s = "abc"; → 編譯期確定,運行時進入常量池,相同內容共享同一對象。String s1 = new String("abc"); → 堆中強制創建新對象,value 指向常量池中的 "abc"。String s = "abc";
String s1 = "abc";
System.out.println(s == s1); // true,同一常量池對象
String s3 = new String("abc");
System.out.println(s == s3); // false,堆對象 vs 常量池對象
System.out.println(s.equals(s3)); // true,equals 比較值內容
public boolean equals(Object anObject) {
if (this == anObject) return true; // 引用相同直接 true
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) { // 長度不同直接 false
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { // 逐字符比較
if (v1[i] != v2[i]) return false;
i++;
}
return true;
}
}
return false;
}
String s = "abc";
String s3 = new String("abc");
System.out.println(s.intern() == s3.intern()); // true,均返回常量池中 "abc" 的引用
intern() 語義:若常量池已有內容相同字符串,返回池中引用;否則將此字符串放入池並返回引用。
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 線程安全 | ✅ 所有公共方法 synchronized | ❌ 無鎖,非線程安全 |
| 引入版本 | JDK 1.0 | JDK 1.5 |
| 適用場景 | 多線程環境下的字符串操作 | 單線程/非併發場景,性能優先 |
| 性能 | 較低(鎖開銷) | 較高(無鎖) |
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value; // 非 final,可擴容
int count; // 當前字符數
// ...
}
兩者均繼承 AbstractStringBuilder,核心操作委託給父類。
public AbstractStringBuilder append(String str) {
if (str == null) return appendNull();
int len = str.length();
ensureCapacityInternal(count + len); // 1. 確保容量,不足則擴容(通常 2倍+2)
str.getChars(0, len, value, count); // 2. 字符拷貝到 value 數組
count += len;
return this;
}
// String.getChars 內部調用
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); // 原生數組拷貝,極快
本質:在可變 char[] 上直接操作,避免了 String 每次操作創建新對象、新數組的開銷。
// String + 拼接:極慢,產生大量臨時對象,觸發頻繁 GC
String s = "";
for (int i = 0; i < 100000; i++) s += "a";
// StringBuilder:毫秒級完成,幾乎無 GC 壓力
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) sb.append("a");
String result = sb.toString();
+,如 "a" + "b" + "c"。intern() 節省內存。byte[] + 編碼標識,ASCII 字符串內存佔用減半,無需代碼修改即享紅利。字符串看似簡單,實則蘊含 JVM 內存管理、性能優化的核心智慧。理解 final char[]、常量池、equals 語義、可變字符序列的數組擴容機制,是寫出高質量 Java 代碼、排查內存/性能問題的必修課。下次再寫 s1 + s2 時,腦海中浮現的應是:「這會不會產生不必要的對象?」