String 是 Java 使用頻率最高的類,也是面試必考、源碼閱讀入門的最佳素材。本文基於 JDK 7/8 源碼,從類定義、屬性結構、構造器族群、核心方法實現、設計權衡 (final、不可變、緩存、共享、內存洩漏修復)、`+` 虛擬機重載、`switch` 支持實現、`intern()` 機制等維度,全方位還原 String 的設計智慧。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** 字符存儲數組 (JDK 9 前為 char[],JDK 9+ 為 byte[] + coder) */
private final char value[];
/** hashCode 緩存,默認 0 表示未計算 */
private int hash;
/** 序列化版本號 */
private static final long serialVersionUID = -6849794470754667710L;
private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];
}
| 決策 | 意圖 | 影響 |
|---|---|---|
final class | 禁止繼承 | 保證不可變性不被破壞;安全敏感場景 (類加載器、線程池參數) 可信任 |
final char[] value | 引用不可變 | 數組對象不可替換,但數組內容理論可修改 (需防護) |
實現 Serializable | 支持序列化 | 網絡傳輸、持久化、RPC 參數傳遞 |
實現 Comparable | 自然排序 | TreeSet/TreeMap key、Collections.sort 直接可用 |
實現 CharSequence | 統一字符序列抽象 | String/StringBuilder/StringBuffer 多態使用 |


public String() { this.value = "".value; }
共享空字符串常量池對象,不建議 `new String()` 再賦值,會產生無用對象。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
直接共享底層數組與 hash,安全因為不可變。極少直接使用,通常用字面量或工廠方法。
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length); // 防護性複製
}
public String(char value[], int offset, int count) {
// 邊界檢查...
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
防護性複製:防止外部修改傳入數組影響 String 內容,保證不可變性。
// 指定 Charset
public String(byte[] bytes, Charset charset) { ... }
// 指定 charsetName
public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException { ... }
// 使用平台默認編碼 (不推薦,跨平台亂碼源頭)
public String(byte[] bytes) { this(bytes, 0, bytes.length); }
核心邏輯:`StringCoding.decode()` 解碼。若未指定編碼,JDK 7 邏輯:優先 `Charset.defaultCharset()`,失敗回退 `ISO-8859-1`。
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
public String(StringBuffer buffer) {
synchronized(buffer) { // StringBuffer 線程安全
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
String(char[] value, boolean share) {
// assert share : "unshared not supported";
this.value = value; // 直接共享數組,無複製
}
曾用於:`substring`、`replace`、`concat`、`valueOf` 等方法,避免數組複製,提升性能、節省內存。
致命缺陷:內存洩漏。長字符串 `substring` 取小片段,原大數組無法 GC。
String huge = readHugeString(); // 10MB String tiny = huge.substring(0, 10); // 僅 10 字符,但引用 10MB 數組 // huge 可回收,但 char[] 無法回收 → 內存洩漏
JDK 7 u6+ 修復:`substring` 改回 `Arrays.copyOfRange`,犧牲性能換安全。
public int length() { return value.length; }
public boolean isEmpty() { return value.length == 0; }
public char charAt(int index) {
if (index < 0 || index >= value.length) throw new StringIndexOutOfBoundsException(index);
return value[index];
}
public boolean equals(Object anObject) {
if (this == anObject) return true; // 1. 引用相等快速返回
if (anObject instanceof String) { // 2. 類型檢查
String another = (String) anObject;
int n = value.length;
if (n == another.value.length) { // 3. 長度快速失敗
char v1[] = value;
char v2[] = another.value;
int i = 0;
while (n-- != 0) { // 4. 逐字符比較
if (v1[i] != v2[i]) return false;
i++;
}
return true;
}
}
return false;
}
優化層次:引用相等 → 類型檢查 → 長度比較 → 內容比較。極大提升不相等情況下的性能。
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
數學本質:`s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]`
為什麼是 31?
衝突必然性:2^32 個哈希值,字符串無限多,鴿巢原理必然衝突。equals 是最終判斷依據。
// JDK 7 u6+ / JDK 8
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) throw new StringIndexOutOfBoundsException(beginIndex);
if (endIndex > value.length) throw new StringIndexOutOfBoundsException(endIndex);
int subLen = endIndex - beginIndex;
if (subLen < 0) throw new StringIndexOutOfBoundsException(subLen);
return (beginIndex == 0 && endIndex == value.length) ? this
: new String(value, beginIndex, subLen); // 防護性複製
}
| 方法 | 參數 | 特點 | 底層 |
|---|---|---|---|
| replace(char, char) | 字符 | 全量替換,非正則 | char[] 複製修改 |
| replace(CharSequence, CharSequence) | 字符序列 | 全量替換,非正則 | Pattern 內部實現 |
| replaceAll(String regex, String replacement) | 正則 | 全量替換,正則 | Pattern/Matcher |
| replaceFirst(String regex, String replacement) | 正則 | 首次替換,正則 | Pattern/Matcher |
陷阱:`replaceAll("$", ".")` 會將 `$` 視為正則行尾符,需轉義 `replaceAll("\\$", ".")`。
public String[] split(String regex, int limit) { ... }
public String[] split(String regex) { return split(regex, 0); }
limit 語義:>0 最多分 limit 段;<0 無限制;=0 無限制且丟棄尾部空串。
// 極度不推薦:依賴平台默認編碼 byte[] bytes = "中文".getBytes(); // 必須顯式指定 byte[] bytes = "中文".getBytes(StandardCharsets.UTF_8);
// String.valueOf 重載
public static String valueOf(int i) { return Integer.toString(i); }
public static String valueOf(Object obj) { return obj == null ? "null" : obj.toString(); }
// Integer.toString 直接調用
// "" + i 實際編譯為
new StringBuilder().append(i).toString();
public native String intern();
機制:String 維護一個全局常量池 (JDK 7 在堆中,JDK 6 在永久代)。調用 `intern()`:池中有等價字符串 → 返回池中引用;無 → 將此字符串引用加入池並返回。
String s1 = new String("abc"); // 堆中新對象
String s2 = s1.intern(); // 常量池中 "abc" 引用
String s3 = "abc"; // 常量池中 "abc" 引用
s2 == s3; // true
// 源碼
String s = "hello" + "world" + user.getName();
// 編譯後 (JDK 8+)
String s = new StringBuilder()
.append("hello")
.append("world")
.append(user.getName())
.toString();
// 常量折疊優化:編譯期確定的字面量直接合併
String s = "helloworld"; // 無 StringBuilder 開銷
// 源碼
switch (str) {
case "hello": ... break;
case "world": ... break;
}
// 編譯後
switch (str.hashCode()) {
case 99162322: // "hello".hashCode()
if (str.equals("hello")) { ... } break;
case 113318802: // "world".hashCode()
if (str.equals("world")) { ... } break;
}
原理:switch 僅支持整型,利用 `hashCode()` 映射為 int,`equals()` 防哈希衝突。性能接近枚舉/整數,字面量比較極快 (引用相等)。
| 場景 | 推薦 | 理由 |
|---|---|---|
| 少量拼接、常量 | String / 字面量 | 編譯期優化、常量池複用 |
| 循環/大量拼接、單線程 | StringBuilder | 非線程安全、性能最高 |
| 多線程共享拼接 | StringBuffer | 線程安全 (synchronized) |
| 頻繁修改字符內容 | StringBuilder | 避免大量 String 對象創建、GC 壓力 |
最佳實踐:
• 不需要新對象時用字面量 `"abc"`
• 需要堆中新對象用 `new String("abc")` (極少見)
• 拼接用 `StringBuilder`
• 跨平台傳輸/存儲顯式指定 `StandardCharsets.UTF_8`
• 大量重複字符串考慮 `intern()` 但需評估常量池內存影響