|專題:java.lang.String 源碼解讀)
一、類概述String是 Java 中使用最頻繁的類代表不可變的字符序列。它的不可變性是 Java 安全模型、字符串常量池和hashCode緩存的基石。繼承體系Object直接父類實現(xiàn)接口Serializable、ComparableString、CharSequence聲明方式public final class Stringfinal禁止被繼承核心職責提供線程安全、可緩存的不可變文本表示類比String就像已經(jīng)刻好的石碑——你可以復制一塊新石碑創(chuàng)建新 String但無法修改原石碑上的字不可變。每次修改都是鑿一塊新石碑。二、關(guān)鍵字段與常量JDK 9 的內(nèi)存布局變化重要// JDK 17 源碼java.lang.String // 核心字段存儲字符的字節(jié)數(shù)組 private final byte[] value; // 編碼標志0 LATIN1每字符1字節(jié)1 UTF16每字符2字節(jié) private final byte coder; // 哈希值緩存默認 0延遲計算 private int hash; // 是否為哈希值 0極少數(shù)真實哈希為 0 的情況下標記用 private boolean hashIsZero; // 編碼常量 static final byte LATIN1 0; static final byte UTF16 1;??JDK 8 vs JDK 9 的重大變化JDK 8內(nèi)部用char[] value每個字符固定占2 字節(jié)UTF-16。JDK 9Compact Strings改為byte[] valuebyte coder。若字符串全為拉丁字母Latin-1每字符僅占1 字節(jié)內(nèi)存節(jié)省約 50%。這是 JEP 254 引入的優(yōu)化對 ASCII 密集型應用如 HTTP 路徑、JSON key效果顯著。三、核心構(gòu)造方法與靜態(tài)工廠// 1. 字面量最常用走常量池 String s1 hello; // 2. 從 char 數(shù)組構(gòu)造會復制數(shù)組保證不可變性 char[] chars {h, e, l, l, o}; String s2 new String(chars); // 3. 從字節(jié)數(shù)組 指定字符集構(gòu)造 byte[] bytes hello.getBytes(StandardCharsets.UTF_8); String s3 new String(bytes, StandardCharsets.UTF_8); // 4. 從 StringBuilder / StringBuffer 構(gòu)造 StringBuilder sb new StringBuilder(hello); String s4 new String(sb); // 5. 靜態(tài)工廠 valueOf對 null 友好返回 null String s5 String.valueOf(42); // 42 String s6 String.valueOf((Object)null); // null不拋 NPE為什么new String(char[])要復制數(shù)組如果不復制外部代碼改了chars[0]就破壞了 String 的不可變性。這是**防御性復制Defensive Copy**設(shè)計模式。四、核心方法源碼級解析4.1equals(Object anObject)— 值相等的判斷public boolean equals(Object anObject) { // 優(yōu)化1引用相等直接返回 true同一對象必然內(nèi)容相同 if (this anObject) { return true; } // 優(yōu)化2快速類型檢查非 String 直接返回 false return (anObject instanceof String aString) (!COMPACT_STRINGS || this.coder aString.coder) StringLatin1.equals(value, aString.value); // 或 StringUTF16.equals }逐行解析this anObject引用比較常量池中的同一對象可以極速返回避免字節(jié)比較開銷。instanceof 模式匹配JDK 16 語法一步完成類型檢查和類型轉(zhuǎn)換。coder比較LATIN1 的 String 和 UTF16 的 String 內(nèi)容編碼不同coder 不同直接排除。最終委托給StringLatin1.equals或StringUTF16.equals進行字節(jié)級比較。4.2hashCode()— 哈希值緩存機制public int hashCode() { int h hash; if (h 0 !hashIsZero) { // 延遲計算第一次調(diào)用時才計算 h isLatin1() ? StringLatin1.hashCode(value) : StringUTF16.hashCode(value); if (h 0) { hashIsZero true; // 真實哈希為0打標記避免每次重算 } else { hash h; // 緩存結(jié)果 } } return h; }數(shù)學公式多項式滾動哈希s[0]*31^(n-1) s[1]*31^(n-2) ... s[n-1]為什么用 3131 是奇質(zhì)數(shù)減少哈希沖突。31 * x可以被 JIT 優(yōu)化為(x 5) - x避免乘法指令性能更好。實驗表明 31 在英文單詞集合上碰撞率極低。??hashIsZero的意義極少數(shù)字符串的哈希值正好是 0如空字符串的哈希是 0 么不是空串hash 0但空串進入的是h 0 !hashIsZero分支會每次重算——這是 JDK 9 之前的一個性能 BugJDK 9 通過hashIsZero標志修復了。4.3intern()— 字符串常量池交互public native String intern();這是一個本地方法native由 JVM 實現(xiàn)檢查字符串常量池String PoolHotSpot 中存于堆的永久代/元空間。若池中已有相同內(nèi)容的字符串返回池中引用。否則將當前字符串放入池中返回該引用。String a new String(hello); // 堆上新對象 String b hello; // 常量池 System.out.println(a b); // false System.out.println(a.intern() b); // true ← intern 后指向同一常量池對象??慎用intern()大量調(diào)用會使常量池膨脹可能導致 GC 壓力增大。適合固定的少量字符串如枚舉鍵不適合動態(tài)內(nèi)容。4.4substring(int beginIndex, int endIndex)— 子串截取public String substring(int beginIndex, int endIndex) { // 邊界檢查 int length length(); checkBoundsBeginEnd(beginIndex, endIndex, length); // 計算子串長度 int subLen endIndex - beginIndex; // 快捷路徑整個字符串 if (beginIndex 0 endIndex length) { return this; } return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen) : StringUTF16.newString(value, beginIndex, subLen); }??JDK 7u6 的重大變化JDK 6 的substring共享原字符串的char[]通過offset和count字段引用會導致內(nèi)存泄漏大字符串無法被 GC。JDK 7u6 后改為復制子數(shù)組徹底解決此問題但每次substring都會產(chǎn)生新數(shù)組時間復雜度 O(n)。4.5String.format/formatted()JDK 15// 傳統(tǒng)方式 String result String.format(Hello, %s! You are %d years old., Alice, 30); // JDK 15 實例方法更簡潔 String result2 Hello, %s! You are %d years old..formatted(Alice, 30);內(nèi)部委托給java.util.Formatter支持%s字符串、%d整數(shù)、%f浮點、%n換行跨平臺等格式符。五、設(shè)計思想與演進特性JDK 8JDK 9–17內(nèi)部存儲char[]固定 2 字節(jié)/字符byte[] coder1 或 2 字節(jié)/字符substringJDK 7u6 起已復制數(shù)組同左無變化String.join已引入JDK 8性能持續(xù)優(yōu)化isBlank/strip不存在JDK 11 引入支持 Unicode 空白repeat(int)不存在JDK 11 引入lines()不存在JDK 11 引入返回StreamStringformatted()不存在JDK 15 引入indent(int)不存在JDK 12 引入文本縮進文本塊不支持JDK 15 正式發(fā)布JEP 378六、易錯點與常見誤區(qū)? 誤區(qū)1用比較字符串內(nèi)容String a new String(hello); String b new String(hello); System.out.println(a b); // false比較的是引用地址 System.out.println(a.equals(b)); // true ?? 誤區(qū)2在循環(huán)中用拼接字符串// ? 每次循環(huán)都創(chuàng)建新 String 對象O(n2) 復雜度 String result ; for (int i 0; i 10000; i) { result i; } // ? 使用 StringBuilderO(n) 復雜度 StringBuilder sb new StringBuilder(); for (int i 0; i 10000; i) { sb.append(i); } String result sb.toString();注意單行字符串拼接如Hello name !由 JDK 編譯器JDK 9 用invokedynamicStringConcatFactory自動優(yōu)化為高效拼接無需手動用StringBuilder。問題在于循環(huán)中累積拼接。? 誤區(qū)3trim()vsstrip()String s \u3000Hello\u3000; // \u3000 是全角空格Unicode 空白 System.out.println(s.trim().length()); // 7沒去掉全角空格 System.out.println(s.strip().length()); // 5去掉了所有 Unicode 空白trim()只處理 ASCII 空白 strip()JDK 11使用Character.isWhitespace()支持 Unicode 空白。? 誤區(qū)4String的不可變不等于線程安全的操作String對象本身不可變但對String類型引用變量的讀寫在多線程環(huán)境下仍需同步volatile或鎖。七、完整小示例import java.util.StringJoiner; import java.util.stream.Collectors; public class StringDemo { public static void main(String[] args) { // 1. 常量池 vs 堆對象 String poolStr java; String heapStr new String(java); System.out.println(poolStr heapStr); // false System.out.println(poolStr heapStr.intern()); // true // 2. JDK 11 新 API String blank ; System.out.println(blank.isBlank()); // true System.out.println(blank.strip()); // System.out.println(ab.repeat(3)); // ababab // 3. 文本塊JDK 15 String json { name: Alice, age: 30 } ; System.out.println(json); // 4. lines() 流式處理 String multiLine line1\nline2\nline3; multiLine.lines() .map(String::toUpperCase) .forEach(System.out::println); // 5. StringJoiner比手動拼接更優(yōu)雅 StringJoiner sj new StringJoiner(, , [, ]); sj.add(apple).add(banana).add(cherry); System.out.println(sj); // [apple, banana, cherry] // 6. hashCode 緩存驗證 String s hello; System.out.println(s.hashCode()); // 99162322 System.out.println(s.hashCode()); // 99162322第二次直接返回緩存 } }八、引申與預告關(guān)聯(lián)類類關(guān)系StringBuilder可變字符序列單線程拼接首選與String可互相轉(zhuǎn)換StringBuffer線程安全版StringBuilder方法均加synchronizedStringJoinerJDK 8 引入專為分隔符拼接設(shè)計底層用StringBuilderjava.util.regex.PatternString.matches()、split()的底層實現(xiàn)者java.util.FormatterString.format()的實際執(zhí)行者StringConcatFactoryJDK 9 編譯器拼接優(yōu)化的核心invokedynamic實現(xiàn)Charset/StandardCharsets字節(jié)數(shù)組 ? 字符串轉(zhuǎn)換時指定編碼 下期專題預告java.lang.StringBuilderStringBuffer明天我們將深入StringBuilder的動態(tài)擴容策略ensureCapacityInternal容量×22的原因append系列方法源碼如何高效追加各種類型StringBuildervsStringBuffervsString三者的性能對比與使用場景選擇JDK 9 的StringConcatFactory編譯器如何用invokedynamic優(yōu)化拼接今日小結(jié)String的不可變性由private final byte[] valuefinal class雙重保障JDK 9 的 Compact Strings 是減少內(nèi)存占用的重要優(yōu)化hashCode的緩存設(shè)計是空間換時間的經(jīng)典案例日常使用中牢記用equals比較內(nèi)容、strip替代trim、循環(huán)內(nèi)用StringBuilder拼接。