十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

坚持看完这套 JDK 源码系列,彻底摆脱 CRUD 开发|专题:java.lang.String 源码解读

坚持看完这套 JDK 源码系列,彻底摆脱 CRUD 开发|专题:java.lang.String 源码解读 一、类概述String是 Java 中使用最频繁的类代表不可变的字符序列。它的不可变性是 Java 安全模型、字符串常量池和hashCode缓存的基石。继承体系Object直接父类实现接口Serializable、ComparableString、CharSequence声明方式public final class Stringfinal禁止被继承核心职责提供线程安全、可缓存的不可变文本表示类比String就像已经刻好的石碑——你可以复制一块新石碑创建新 String但无法修改原石碑上的字不可变。每次修改都是凿一块新石碑。二、关键字段与常量JDK 9 的内存布局变化重要// JDK 17 源码java.lang.String // 核心字段存储字符的字节数组 private final byte[] value; // 编码标志0 LATIN1每字符1字节1 UTF16每字符2字节 private final byte coder; // 哈希值缓存默认 0延迟计算 private int hash; // 是否为哈希值 0极少数真实哈希为 0 的情况下标记用 private boolean hashIsZero; // 编码常量 static final byte LATIN1 0; static final byte UTF16 1;⚠️JDK 8 vs JDK 9 的重大变化JDK 8内部用char[] value每个字符固定占2 字节UTF-16。JDK 9Compact Strings改为byte[] valuebyte coder。若字符串全为拉丁字母Latin-1每字符仅占1 字节内存节省约 50%。这是 JEP 254 引入的优化对 ASCII 密集型应用如 HTTP 路径、JSON key效果显著。三、核心构造方法与静态工厂// 1. 字面量最常用走常量池 String s1 hello; // 2. 从 char 数组构造会复制数组保证不可变性 char[] chars {h, e, l, l, o}; String s2 new String(chars); // 3. 从字节数组 指定字符集构造 byte[] bytes hello.getBytes(StandardCharsets.UTF_8); String s3 new String(bytes, StandardCharsets.UTF_8); // 4. 从 StringBuilder / StringBuffer 构造 StringBuilder sb new StringBuilder(hello); String s4 new String(sb); // 5. 静态工厂 valueOf对 null 友好返回 null String s5 String.valueOf(42); // 42 String s6 String.valueOf((Object)null); // null不抛 NPE为什么new String(char[])要复制数组如果不复制外部代码改了chars[0]就破坏了 String 的不可变性。这是**防御性复制Defensive Copy**设计模式。四、核心方法源码级解析4.1equals(Object anObject)— 值相等的判断public boolean equals(Object anObject) { // 优化1引用相等直接返回 true同一对象必然内容相同 if (this anObject) { return true; } // 优化2快速类型检查非 String 直接返回 false return (anObject instanceof String aString) (!COMPACT_STRINGS || this.coder aString.coder) StringLatin1.equals(value, aString.value); // 或 StringUTF16.equals }逐行解析this anObject引用比较常量池中的同一对象可以极速返回避免字节比较开销。instanceof 模式匹配JDK 16 语法一步完成类型检查和类型转换。coder比较LATIN1 的 String 和 UTF16 的 String 内容编码不同coder 不同直接排除。最终委托给StringLatin1.equals或StringUTF16.equals进行字节级比较。4.2hashCode()— 哈希值缓存机制public int hashCode() { int h hash; if (h 0 !hashIsZero) { // 延迟计算第一次调用时才计算 h isLatin1() ? StringLatin1.hashCode(value) : StringUTF16.hashCode(value); if (h 0) { hashIsZero true; // 真实哈希为0打标记避免每次重算 } else { hash h; // 缓存结果 } } return h; }数学公式多项式滚动哈希s[0]*31^(n-1) s[1]*31^(n-2) ... s[n-1]为什么用 3131 是奇质数减少哈希冲突。31 * x可以被 JIT 优化为(x 5) - x避免乘法指令性能更好。实验表明 31 在英文单词集合上碰撞率极低。⚠️hashIsZero的意义极少数字符串的哈希值正好是 0如空字符串的哈希是 0 么不是空串hash 0但空串进入的是h 0 !hashIsZero分支会每次重算——这是 JDK 9 之前的一个性能 BugJDK 9 通过hashIsZero标志修复了。4.3intern()— 字符串常量池交互public native String intern();这是一个本地方法native由 JVM 实现检查字符串常量池String PoolHotSpot 中存于堆的永久代/元空间。若池中已有相同内容的字符串返回池中引用。否则将当前字符串放入池中返回该引用。String a new String(hello); // 堆上新对象 String b hello; // 常量池 System.out.println(a b); // false System.out.println(a.intern() b); // true ← intern 后指向同一常量池对象⚠️慎用intern()大量调用会使常量池膨胀可能导致 GC 压力增大。适合固定的少量字符串如枚举键不适合动态内容。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字段引用会导致内存泄漏大字符串无法被 GC。JDK 7u6 后改为复制子数组彻底解决此问题但每次substring都会产生新数组时间复杂度 O(n)。4.5String.format/formatted()JDK 15// 传统方式 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);内部委托给java.util.Formatter支持%s字符串、%d整数、%f浮点、%n换行跨平台等格式符。五、设计思想与演进特性JDK 8JDK 9–17内部存储char[]固定 2 字节/字符byte[] coder1 或 2 字节/字符substringJDK 7u6 起已复制数组同左无变化String.join已引入JDK 8性能持续优化isBlank/strip不存在JDK 11 引入支持 Unicode 空白repeat(int)不存在JDK 11 引入lines()不存在JDK 11 引入返回StreamStringformatted()不存在JDK 15 引入indent(int)不存在JDK 12 引入文本缩进文本块不支持JDK 15 正式发布JEP 378六、易错点与常见误区❌ 误区1用比较字符串内容String a new String(hello); String b new String(hello); System.out.println(a b); // false比较的是引用地址 System.out.println(a.equals(b)); // true ✓❌ 误区2在循环中用拼接字符串// ❌ 每次循环都创建新 String 对象O(n²) 复杂度 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自动优化为高效拼接无需手动用StringBuilder。问题在于循环中累积拼接。❌ 误区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 空白。❌ 误区4String的不可变不等于线程安全的操作String对象本身不可变但对String类型引用变量的读写在多线程环境下仍需同步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比手动拼接更优雅 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第二次直接返回缓存 } }八、引申与预告关联类类关系StringBuilder可变字符序列单线程拼接首选与String可互相转换StringBuffer线程安全版StringBuilder方法均加synchronizedStringJoinerJDK 8 引入专为分隔符拼接设计底层用StringBuilderjava.util.regex.PatternString.matches()、split()的底层实现者java.util.FormatterString.format()的实际执行者StringConcatFactoryJDK 9 编译器拼接优化的核心invokedynamic实现Charset/StandardCharsets字节数组 ↔ 字符串转换时指定编码 下期专题预告java.lang.StringBuilderStringBuffer明天我们将深入StringBuilder的动态扩容策略ensureCapacityInternal容量×22的原因append系列方法源码如何高效追加各种类型StringBuildervsStringBuffervsString三者的性能对比与使用场景选择JDK 9 的StringConcatFactory编译器如何用invokedynamic优化拼接今日小结String的不可变性由private final byte[] valuefinal class双重保障JDK 9 的 Compact Strings 是减少内存占用的重要优化hashCode的缓存设计是空间换时间的经典案例日常使用中牢记用equals比较内容、strip替代trim、循环内用StringBuilder拼接。
返回列表