bigywater.tech
Java 内部原理 · 52

String.intern() 真实验证:字符串常量池到底存的是谁

"字符串常量池"这个词几乎每个 Java 面试都会问,但很少有人真的用 == 一行行验证过每种写法的真实行为。这篇用 9 组对比真实跑出字面量、new String()、编译期拼接、运行时拼接、intern() 各自的真实结果。

new String() 真实不进池
真实验证:new String("hello") == "hello" 结果是 false,只有调用 intern() 后才和字面量相等
编译期拼接 = 字面量
真实验证:"hel"+"lo" 这种编译期常量拼接和字面量 "hello" 是同一个对象,但运行时变量拼接不是

上一篇:Java 泛型擦除真实验证

真实验证:字面量、new String()、intern() 三者关系

用 == 比较对象引用,真实观察谁和谁是同一个对象。

StringInternDemo.java String a = "hello"; String b = "hello"; String c = new String("hello"); String d = c.intern();
真实实测
a == b (两个字面量): true
c == a (new String vs 字面量): false
c.equals(a): true
d == a (c.intern() vs 字面量): true
d == c (intern() 返回值 vs new 出来的原对象): false

真实结果很清楚:两个相同内容的字面量 "hello" 编译期就被折进同一个常量池条目,== 真实为 true。new String("hello") 则真实在堆上创建了一个新对象,和常量池里的字面量不是同一个。调用 c.intern() 后,由于常量池里已经有 "hello" 这个条目(是字面量 a 放进去的),intern() 真实返回的就是池里已有的那个引用,和 c 本身(堆上的新对象)不是同一个,但和 a 是同一个。

真实验证:编译期拼接和运行时拼接的真实差异

同样是拼出 "hello",编译期能确定结果的拼接和运行时才能确定结果的拼接,是否进常量池是两回事。

StringInternDemo.java String e = "hel" + "lo"; // 编译期常量拼接 String part = "hel"; String f = part + "lo"; // 运行时变量拼接 String h = new StringBuilder("hel").append("lo").toString();
真实实测
e == a (编译期常量拼接): true
f == a (运行时变量拼接): false
g == a (运行时拼接结果 intern 后): true
h == a (StringBuilder.toString()): false
h.intern() == a: true

真实结果证明了一个常被记混的规则:"hel" + "lo" 里两个操作数都是编译期常量,javac 会在编译期直接把它们折叠成 "hello" 这一个字面量,所以和 a 是同一个对象。但只要拼接里出现了一个变量(part),哪怕它的值在运行期间从未改变,编译器也无法在编译期确定结果,只能真实生成运行时拼接(等价于 StringBuilder),结果对象就落在堆上,和常量池里的字面量不是同一个,必须手动 intern() 才能"合并"回去。

真实验证:大量不同字符串 intern() 的真实耗时

JDK 7 之后字符串常量池搬进了堆,intern() 不再是一个"危险的永久代操作",这里真实测一下量级。

真实实测
20万次不同字符串 intern() 耗时: 44ms
什么时候该用 intern()。常量池底层是一个哈希表(StringTable),20 万次各不相同的 intern() 真实只花了 44ms,量级上不算重,但如果在一个长期运行的服务里对着海量、基数很大的动态字符串(比如用户输入)无脑调用 intern(),常量池会持续膨胀,依然会真实占用堆内存并增加哈希冲突——它更适合用在"取值范围有限、重复率高"的场景,比如反序列化出来的固定字段名。

分步演示:哪些写法真实回到了 #1,哪些没有

用 identityHashCode 给每个字符串对象编号,串成动画:编号相同就是常量池里的同一个对象。

字面量、new String()、编译期拼接、运行时拼接、intern():九种写法,编号是怎么合并又怎么分开的
这一步新出现或发生变化的变量

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,intern() 耗时数据重复运行量级一致。
    • 和String 不可变性那篇一起看,能拼出字符串在 JVM 里从创建到常量池登记的完整真实链路。
    • 没有涉及:CompactStrings(JDK9+ 字符串压缩存储)对常量池内存占用的真实影响、StringTable 默认桶数量与 -XX:StringTableSize 调优——这两个话题留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号