全网最通透的 String 源码解读!看完这篇,面试再也不怕被问 String
-
为什么 String 不可变?
-
JDK9 为什么改成 byte []?
-
hashCode 为什么用 31?
-
substring 有什么坑?
一、先看 String 本质:为什么设计成不可变?
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {// JDK8// private final char value[];// JDK9+private final byte[] value;private final byte coder;}
-
类被 final:不能被继承,没有子类
-
底层数组被 final:引用不能改,内容也不提供修改入口
-
所有方法都不修改原对象:截取、替换、切割全是返回新 String
-
线程安全,不用加锁
-
适合做 HashMap key
-
字符串常量池可以安全复用
二、JDK8 → JDK9:String 内存大优化
JDK8 及以前
privatefinalcharvalue[];
-
char 固定2 字节
-
英文、数字也占 2 字节 → 内存浪费
JDK9 及以后(现在主流)
privatefinalbyte[]value;privatefinalbytecoder;
-
用byte[]存储
-
coder 标记编码:
-
0:Latin-1(英文数字,1 字节)
-
1:UTF-16(中文等,2 字节)
三、hashCode ():31 这个魔数到底为啥?
private int hash;publicinthashCode() {int h = hash;if (h == 0 && value.length > 0) {byte[] val = value;for (int i = 0; i < value.length; i++) {h = 31 * h + (val[i] & 0xff);}hash = h;}return h;}
-
hash 缓存:算一次就存起来,不再重复算
-
乘 31:这就是 String 天生适合做HashMap 键的原因。
四、高频方法:源码 + 实战一把抓
1. substring 截取(左闭右开)
public String substring(int beginIndex, int endIndex) {checkBoundsBeginEnd(beginIndex, endIndex, length());if (beginIndex == 0 && endIndex == length())return this;return isLatin1()? StringLatin1.newString(...): StringUTF16.newString(...);}
String str = "Hello,Java-String-云扬";String sub1 = str.substring(6, 10);String sub2 = str.substring(6);System.out.println(sub1); // JavaSystem.out.println(sub2); // Java-String-云扬
注意:截取都会返回新字符串。
2. indexOf 查找
String str = "java-string-cloud-java";int idx1 = str.indexOf('j'); // 0int idx2 = str.indexOf("string"); // 5int idx3 = str.indexOf('j', 5); // 18
3. 必用工具方法
String str = "JavaBlog";str.length(); // 长度str.isEmpty(); // 是否空str.charAt(2); // 取第3个字符str.trim(); // 去首尾空格String.valueOf(1024); // 任意类型转字符串
五、一张图总结 String 核心
-
final 类 + final 数组 = 不可变
-
JDK9+ byte[] + coder = 省内存
-
hashCode 缓存 + 31 哈希 = 适合做 Map Key
-
所有修改都返回新对象 = 安全、线程安全
-
少量拼接:直接 +
-
大量拼接 / 循环拼接:StringBuilder
我是云扬,专注Java 后端 & 源码实战。干货持续更新,建议星标 / 收藏,面试前直接翻出来看。
夜雨聆风