【Java】String 源码深度解读:从设计原理到核心方法实战
在 Java 后端开发中,String 是使用频率最高、面试出镜率最高的基础类之一。绝大多数业务逻辑都离不开字符串拼接、截取、查找、比较等操作,但很多开发者只停留在 API 调用层面,对其底层设计、JDK 优化、源码逻辑并不了解。
本文带你逐行剖析 String 核心源码,搞懂它的不可变性、底层存储优化、常用方法原理,并搭配实战代码,吃透 Java 字符串。
一、String 类的核心声明
先看 String 最顶层的类定义,这是理解它所有特性的基础:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
关键特性解读
final修饰String 无法被继承,不存在子类,这是字符串不可变性的基础保障。- 实现
Serializable支持序列化与反序列化,可在网络传输、本地存储中使用。 - 实现
Comparable<String>支持自然排序,字符串比较推荐使用compareTo(),而非==(==比较对象地址,equals()比较内容)。 - 实现
CharSequence与StringBuffer、StringBuilder同属一个接口,三者为 “近亲”:后两者是可变字符串,适合频繁拼接。
二、底层存储优化:JDK8 char [] → JDK9+ byte []
String 的底层存储结构,在 JDK 版本迭代中有关键内存优化,这是面试与性能调优的高频考点。
1. JDK 8 及之前
private final char value[];
char类型在 JVM 中固定占 2 字节- 即使是纯英文、数字(单字节可存储),也会占用 2 字节,内存浪费严重
2. JDK 9 及之后(主流版本)
private final byte[] value;
private final byte coder;
- 底层改为
byte[]存储,单字节存储,大幅节省内存 - 新增
coder字段区分编码:Latin-1:单字节编码,存储英文、数字UTF-16:双字节编码,存储中文等特殊字符
- 优势:内存占用降低,GC 频率减少,程序性能提升
三、hashCode ():31 倍哈希法与缓存机制
String 重写了 hashCode(),是它适合作为 HashMap 键的核心原因。
核心源码
private int hash; // 缓存哈希值,默认0
public int hashCode() {
int h = hash;
// 未计算过且字符串非空,才重新计算
if (h == 0 && value.length > 0) {
byte[] val = value;
// 31倍哈希法
for (int i = 0; i < value.length; i++) {
h = 31 * h + (val[i] & 0xff);
}
hash = h; // 缓存结果,避免重复计算
}
return h;
}
关键点
- 哈希缓存:计算一次后永久缓存,提升效率
- 31 倍哈希法
- 31 是质数,哈希冲突概率低
31 * i = (i << 5) - i,JVM 会自动优化,计算极快
- 哈希值分布均匀,让
HashMap读写效率最优
四、substring ():字符串截取的底层逻辑
substring() 是日常开发最常用的截取方法,源码核心做边界校验 + 新建字符串。
简化源码(JDK11)
public String substring(int beginIndex, int endIndex) {
// 校验索引合法性
checkBoundsBeginEnd(beginIndex, endIndex, length());
// 截取整个字符串,直接返回自身
if (beginIndex == 0 && endIndex == length()) {
return this;
}
// 根据编码创建新字符串
return isLatin1() ? StringLatin1.newString(value, beginIndex, endIndex - beginIndex)
: StringUTF16.newString(value, beginIndex, endIndex - beginIndex);
}
实战代码示例
public class SubstringDemo {
public static void main(String[] args) {
String str = "Hello,Java-String-Cloud";
// 截取 [6, 10) → 左闭右开
String sub1 = str.substring(6, 10);
// 从索引7截取到末尾
String sub2 = str.substring(6);
System.out.println(sub1); // Java
System.out.println(sub2); // Java-String-Cloud
}
}
注意:
substring()一定会返回新的 String 对象(除非截取整个原串)。
五、indexOf ():字符 / 子串查找原理
indexOf() 用于查找字符或子串第一次出现的索引,找不到返回 -1。
实战代码示例
public class IndexOfDemo {
public static void main(String[] args) {
String str = "java-string-cloud-java";
// 查找字符'j'第一次出现位置
int idx1 = str.indexOf('j');
// 查找子串"string"的起始索引
int idx2 = str.indexOf("string");
// 从索引5开始,查找字符'j'
int idx3 = str.indexOf('j', 5);
System.out.println(idx1); // 0
System.out.println(idx2); // 5
System.out.println(idx3); // 18
}
}
底层逻辑:遍历字节数组,逐位匹配,找到即返回索引,效率为 O (n)。
六、高频常用方法 & 极简实战
1. 基础判断方法
String str = "JavaBlog";
// 字符串长度
System.out.println(str.length()); // 8
// 判断是否为空串
System.out.println(str.isEmpty()); // false
// 获取指定索引字符
System.out.println(str.charAt(2)); // v
2. 类型转换:valueOf ()
valueOf() 是将任意类型转为字符串的标准方法,底层调用对应包装类 toString():
int num = 1024;
String str1 = String.valueOf(num); // "1024"
double d = 3.14;
String str2 = String.valueOf(d); // "3.14"
3. 字节转换:getBytes ()
String str = "云扬";
// 转UTF-8字节数组
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(str.getBytes(StandardCharsets.UTF_8)));
4. 去空格:trim ()
去除字符串首尾空白字符(空格、制表符、换行符等):
String str = " Java String ";
String trimStr = str.trim(); // "Java String"
七、总结(核心考点)
- 不可变性:
final类 + 底层数组不可修改,线程安全 - 存储优化:JDK9+ 用
byte[] + coder替代char[],节省内存 - 哈希缓存:
hashCode()只计算一次,适合做HashMap键 - 常用方法:均带索引边界校验,避免
StringIndexOutOfBoundsException - 拼接建议:频繁字符串拼接,优先使用
StringBuilder



