Java String 源碼深度解析
java.lang.String 是 Java 使用頻率最高、面試必考的核心類別。本文基於 JDK 17 源碼,深度剖析不可變性實現、底層儲存演進、字串常量池、核心方法實現與效能優化陷阱。
不可變性鐵三角設計
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
// 1. final 類:不可被繼承,防止子類破壞不可變語義
// 2. final 欄位:引用不可變更
// 3. 無 setter / 修改狀態方法:所有「修改」操作回傳新實例
// JDK 8 及之前
private final char value[]; // 字元陣列
private final int hash; // 緩存 hashCode (默認 0 表示未計算)
// JDK 9+ (Compact Strings) - 節省記憶體
private final byte[] value; // 位元組陣列
private final byte coder; // 0=LATIN1(1 byte/char), 1=UTF16(2 bytes/char)
private final int hash; // 同樣緩存
}
核心構造與建立路徑
| 方式 | 記憶體行為 | 常量池關係 |
| 字面量 `"abc"` | 編譯期進入常量池,執行期直接引用 | 自動駐留 |
| `new String("abc")` | 堆上新建物件,`value` 指向常量池字元陣列 (JDK 8) 或複製 (JDK 9+) | 不自動駐留 |
| `intern()` | 若常量池有相同內容返回引用,否則放入並返回 | 手動駐留 |
關鍵源碼片段解析
1. hashCode 緩存計算
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
h = isLatin1() ? hashCodeLatin1() : hashCodeUTF16();
hash = h; // 欄位非 final 但邏輯不可變,利用 racy single-check 免鎖
}
return h;
}
private int hashCodeLatin1() {
int h = 0;
for (byte v : value) h = 31 * h + (v & 0xff);
return h;
}
利用 hash == 0 為未計算標記,無需同步,極少數線程重複計算可接受。
2. equals 高效比較
public boolean equals(Object anObject) {
if (this == anObject) return true; // 同引用快速返回
if (anObject instanceof String aString) { // 模式匹配 (JDK 16+)
return isLatin1() == aString.isLatin1() // 編碼一致才比較
&& Arrays.equals(value, aString.value);
}
return false;
}
先比引用、再比編碼標識、最後比底層陣列,極致優化。
3. substring 演進:JDK 6 vs 7+
- JDK 6:共享 `value` 陣列,`offset` + `count` 定義視窗 → 長字串 `substring` 導致原大陣列無法 GC(洩漏隱患)。
- JDK 7+:複製新陣列,獨立擁有 → 空間換時間,消除洩漏風險。
4. 連接優化:StringBuilder 自動介入
String s = "a" + "b" + "c"; // 編譯期常量折疊 → "abc"
String s = a + b + c; // 編譯器生成 StringBuilder.append().toString()
String s = ""; for (..) s += x; // ❌ 循環內每次新建 StringBuilder,應手動提取
字串常量池與 intern() 實戰
String s1 = "abc";
String s2 = new String("abc");
String s3 = s2.intern();
s1 == s2; // false (堆 vs 常量池)
s1 == s3; // true (intern 返回常量池引用)
// JDK 7+ 常量池移至堆中,GC 可回收無引用駐留字串
// 大量 intern() 可能導致 OOM,慎用
效能陷阱與最佳實踐
- 循環拼接用 StringBuilder:預估容量 `new StringBuilder(1024)` 避免擴容。
- 避免 `new String("literal")`:雙重物件浪費,直接用字面量。
- 正則分割/替換慎用 `String.split/replace`:內部編譯 Pattern,熱點路徑預編譯 `Pattern.compile` 復用。
- 大字串切片:JDK 7+ 複製陣列,超大字串頻繁 `substring` 考慮 `CharSequence` 視圖或 `StringBuilder` 操作。
- 序列化:`String` 實作 `Serializable`,`serialVersionUID` 固定,跨版本穩定。
JDK 9+ Compact Strings 記憶體省 50%+
- 純 ASCII/Latin-1 字串:1 byte/char,`coder=0`。
- 含補充字符:2 bytes/char,`coder=1` (UTF-16)。
- 自動偵測,無需代碼變更,透明節省堆空間。
透徹掌握 String 源碼設計哲學,能寫出高效、無 Bug 的字串處理代碼,面試與架構設計雙贏。
- 上一篇文章: JSON 核心語法與 JavaScript 解析全攻略:數據類型、示例、三種解析方式對比
- 下一篇文章: 工廠模式實戰:支付系統多渠道統一接口設計與 Spring 整合最佳實踐
Copyright ©
程式碼之家 All Rights Reserved