在 Java 开发中,String 类是日常使用频率极高的类之一。从简单的字符串拼接到复杂的文本处理,都离不开它的身影。但很多时候,我们只是停留在'会用'的层面,对其底层源码的设计逻辑和特性了解甚少。
一、String 类的核心特性:不可变性
打开 JDK 源码,我们会发现 String 类被 final 关键字修饰,这意味着 String 类不能被继承。同时,其内部存储字符串的成员变量 value(在 JDK 1.8 及之前为 char[] 类型,JDK 1.9 之后改为 byte[] 类型)也被 private final 修饰。这两个关键设计,共同决定了 String 类的核心特性——不可变性。
// JDK 1.8 String 类核心成员变量
private final char value[];
// JDK 1.9 String 类核心成员变量
private final byte[] value;
private final byte coder; // 用于标识编码格式,0 表示 Latin-1,1 表示 UTF-16
不可变性意味着一旦 String 对象被创建,其内部的字符序列就无法被修改。比如执行 String str = "abc"; str += "d"; 时,看似是修改了 str 的值,实则是创建了一个新的 String 对象 "abcd",并将 str 的引用指向了这个新对象,原对象 "abc" 依然存在于内存中(后续会被垃圾回收机制处理)。
这种设计带来了诸多好处:
- 线程安全:由于对象不可修改,多线程环境下无需担心线程安全问题,无需额外加锁。
- 可缓存哈希值:String 类的 hashCode() 方法会根据 value 计算哈希值,由于 value 不可变,哈希值只需计算一次并缓存起来,后续调用 hashCode() 可直接返回,提升效率(这也是 HashMap 等集合中常用 String 作为键的重要原因)。
- 常量池复用:Java 中的字符串常量池(String Pool)可以复用相同内容的 String 对象,减少内存占用。比如
String a = "abc"; String b = "abc";,a 和 b 会指向常量池中同一个对象。
二、底层存储的演变:从 char[] 到 byte[]
在 JDK 1.8 及之前,String 类使用 char[] 存储字符,每个 char 占 2 个字节(UTF-16 编码)。但在实际开发中,大部分字符串是由 Latin-1 字符(如英文字母、数字、常见符号)组成,这类字符只需 1 个字节就能存储。使用 char[] 存储会造成大量内存浪费。
为了解决这个问题,JDK 1.9 对 String 的底层存储进行了优化,将 char[] 改为 byte[],并新增了 coder 成员变量来标识编码格式:
- 当 coder = 0 时,使用 Latin-1 编码,每个字符占 1 个字节,适用于存储英文字符等。
- 当 coder = 1 时,使用 UTF-16 编码,每个字符占 2 个字节,适用于存储中文、特殊符号等。
这种自适应编码的设计,在保证功能不受影响的前提下,大幅减少了内存占用,尤其在处理大量英文字符串的场景下,优化效果显著。
三、常用方法的源码解析
1. equals() 方法:判断字符串内容是否相等
String 类重写了 Object 类的 equals() 方法,其核心逻辑是逐字符比较两个字符串的内容是否一致,具体步骤如下:
- 先判断两个对象的引用是否相同,若相同直接返回 true。
- 判断传入的对象是否为 String 类型,若不是直接返回 false。
- 比较两个字符串的长度和编码格式(JDK 1.9 及之后),若不一致返回 false。


