java.lang.String 是 Java 使用頻率最高的類別之一。理解其不可變設計、內部結構、核心方法實現與效能特性,對寫出高品質 Java 代碼至關重要。本文基於 JDK 17+ 源碼深度解析。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** 字元儲存陣列 (JDK 9+ 為 byte[] + coder,節省記憶體) */
@Stable
private final byte[] value; // JDK 9+ Compact Strings
/** 編碼標識:0=LATIN1(ISO-8859-1), 1=UTF-16 */
private final byte coder;
/** 雜湊碼快取,0 表示未計算 */
private int hash;
// ... 建構子與方法
}
value、coder 初始化後不可變,保證執行緒安全byte[] (1 byte/char),含 Unicode 字元才用 char[] 等效的 byte[] (2 bytes/char),大幅降低記憶體佔用hash 欄位避免重複計算 hashCode(),首次計算後永久快取| 方式 | 範例 | 說明 |
|---|---|---|
| 字面常量 | String s = "Hello"; | 編譯期確定,存入字串常量池,相同內容共用引用 |
new String() | new String("Hello") | 運行期在堆上建立新物件,不進入常量池,除非呼叫 intern() |
valueOf() | String.valueOf(123) | 靜態工廠方法,處理 null 安全,各基本型別均有重載 |
| 字元陣列 | new String(char[]) | 複製字元陣列內容,確保不可變性 |
public boolean equals(Object anObject) {
if (this == anObject) return true; // 同一引用快速返回
if (anObject instanceof String) {
String another = (String) anObject;
if (this.coder == another.coder) { // 相同編碼直接比較位元組
return equals(value, another.value);
}
return false; // 編碼不同必不相等
}
return false;
}
// 位元組陣列逐位元組比較 (內部靜態方法)
private static boolean equals(byte[] v1, byte[] v2) {
if (v1.length != v2.length) return false;
for (int i = 0; i < v1.length; i++) {
if (v1[i] != v2[i]) return false;
}
return true;
}
效能優化:先比長度、編碼,再逐位元組比較,利用 CPU 快取局部性。
public int hashCode() {
int h = hash;
if (h == 0 && !value.length == 0) {
hash = h = isLatin1() ? hashCode(value) : hashCode(value);
}
return h;
}
private static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]31*i == (i<<5)-i,JVM 可優化為位移與減法hash 欄位,後續呼叫 O(1)JDK 6 舊實現:共用底層 char[],透過 offset、count 視窗。缺點:長字串的小子串會阻礙原大字串被 GC,導致記憶體洩漏。
JDK 7+ 新實現:完全複製所需字元範圍到新陣列。優點:子串獨立,無記憶體洩漏風險。代價:O(n) 時間與空間。
// concat 實現:建立新 byte[] 複製雙方內容
public String concat(String str) {
int len = str.length();
if (len == 0) return this;
byte[] buf = new byte[value.length + str.value.length];
System.arraycopy(value, 0, buf, 0, value.length);
System.arraycopy(str.value, 0, buf, value.length, str.value.length);
return new String(buf, coder);
}
+ 操作符編譯期優化為 StringBuilder.append() 鏈式呼叫,單行表達式效能最佳concat() 適合簡單兩字串拼接,無建構子開銷public String[] split(String regex, int limit) {
// 簡單單字元分隔符 (非正則元字元) 走快速路徑
// 否則委派給 Pattern.compile(regex).split(this, limit)
}
效能建議:
",", "|") 效能極佳,走快速路徑Pattern 重複使用StringTokenizer (舊 API,不推薦新代碼)String joined = String.join(", ", "apple", "banana", "orange");
// "apple, banana, orange"
List list = List.of("a", "b", "c");
String joined2 = String.join("-", list);
// "a-b-c"
內部使用 StringJoiner,自動處理分隔符邏輯,程式碼簡潔優雅。
String s1 = "Hello";
String s2 = new String("Hello");
String s3 = s2.intern();
System.out.println(s1 == s2); // false (堆 vs 常量池)
System.out.println(s1 == s3); // true (intern 返回常量池引用)
intern():若常量池已有相同內容返回池中引用,否則將此字串加入池並返回引用intern(),但需注意同步鎖爭用效能影響new StringBuilder(1024) 避免擴容String s = new String("literal") → String s = "literal"indexOf + substring 避免正則開銷if (s1.hashCode() == s2.hashCode() && s1.equals(s2))| 方法 | 描述 |
|---|---|
isBlank() | 判斷空白字串 (含空白字元),替代 trim().isEmpty() |
strip() | 移除首尾空白字元 (Unicode 感知),優於 trim() |
lines() | 返回行流 Stream,支援多種換行符 |
repeat(int) | 重複字串 N 次 |
indent(int) | 調整每行縮進 (正數增加、負數減少) |
transform(Function) | 函數式轉換鏈式呼叫 |