是什么?
StringTable / String pool也称为字符串常量池 ,用来专门放字符串常量的。
大家都知道,在Java中我们经常会去创建字符串对象,而每创建一个字符串对象都需要消耗一定的内存空间,这个时候就出现了字符串常量池,当我们池中存在这个字符串时,就直接可以使用池中的这个,不需要再去创建一个。
String s1 = "a";
String s2 = "a";
// s1 == s2 ---> true
在JDK1.6之前,StringTable被存放在老年代中,而在JDK1.7之后,StringTable被存放在堆中。
String常量是什么时候被放入StringTable中的?
看下面这段代码:
String s1 = "a";
String s2 = "b";
String s3 = "ab";
其实在该类被初始化时,这些字符串常量并没有立刻被方法StringTable中,它只是作为一个符号被放到常量池中了,注意是符号!这个时候还没有创建对象。比如上述代码,在类被加载的时候,将 a b ab 这三个符号存储到了常量池中,如下所示:
通过
javap -v [].class
来反编译字节码文件。
这是因为java字符串采用了延迟加载,也就是懒加载的模式,只有在执行到对应的代码,比如在执行String s1 = "a"
这行代码的时候,才会先去串池中查找a这个字符串存不存在,如果存在直接使用,如果不存在,那就创建"a"字符串对象到池中。
new String()做了啥?
比如,new String("a")
,这行代码首先在堆中肯定是存放了一份String对象,并且将"a"字符串对象也放入了串池中,也就是创建了两个对象分别在堆和非堆当中。
不一样的是,String s = "a"
,是直接将"a"存入串池中,前提是串池中没有"a",并不会创建在堆中,只是在串池放入"a"字符串对象。
String# intern方法
为了引出我所遇到的StringTable中的坑,直接来说一下这个方法。
首页介绍一下它的作用:
该方法用来尝试将一个字符串对象放入串池中。 如果串池中存在这个字符串对象(它是用当前调用方法的字符串对象.equals(池中字符串对象) == true来判断的),那就返回池中字符串对象的引用。
注意,在JDK1.7之后,如果池中不存在,不会立即创建,而是先去堆中查找该字符串对象存不存在,如果存在则将堆中字符串的引用
存到池中,注意这里是引用!
StringTable串池中到底存的是引用还是对象?
读到这里,想必你也会有这个疑问,StringTable串池中到底存的是引用还是对象?
首页,这一点可以明确的告诉你,**runtime constant pool(运行常量池)**中存放的是引用!!但是StringTable/StringPool可不好说…
某大神R的回答:
原文链接:https://www.zhihu.com/question/57109429
网络上关于这个问题的解释,我几乎翻了个遍,回答不一,有说存对象,有说存引用,也就说即存放对象又存放引用…在踩过一些坑之后,个人总结:即存放对象,又存放引用。
intern()的源码就说了
它说的很清楚,将StringObject放入串池中。
而上面又说过:
这两点是我总结的依据之一。
我想在这里就告诉你这一观点,否则下面的案例代码,你可能会很懵。
先从简单开始
以下代码基于JDK1.8。
String s = new String("1");
String s2 = "1";
String intern = s.intern();
System.out.println(s == intern); // false
System.out.println(s == s2); // false
System.out.println(s2 == intern); // true
分析一下:
String s = new String("1")
,创建String字符串对象实例到堆中,将"1"创建到串池中,s指向堆中的字符串对象;String s2 = "1"
,先去串池中找是否存在字符串"1",发现有,则直接使用串池中的这个;String intern = s.intern()
,将s尝试放入串池中,实际上串池有有这个"1",所以这边返回"1"这个字符串对象在串池中的引用,也就是intern指向了串池中的"1"。
其实这段代码没什么好分析的,看图就一目了然了,确实很简单,但是先别着急离开。
下一个例子:
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4); // true
分析:
String s3 = new String("1") + new String("1")
,其实这段代码分别执行了两次 new String(“1”),在串池中创建了"1"这个字符串对象,最后调用了new String(“11”, 0, count),也就是在堆中创建了一个"11"字符串对象实例,让s3引用指向它,如果你有疑惑,为什么new String(“11”, 0, count)只在堆中创建了实例,串池中没有创建"11"字符串对象呢?请看下面StringBuidler中toString方法的介绍,相信你会明白我的解释。
s3.intern()
,将s3存入串池中,这个时候串池中只有"1",并没有匹配的"11"在串池中,所以它会先去堆中找"11",发现,找到了!所以,将堆中"11"字符串对象的引用存到了串池中;String s4 = "11"
,直接去串池中寻找有没有"11",发现有,就用它!于是s4引用指向了串池中的保存的引用,如图。
将s3.intern();
和下一行换一下顺序?
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4); // false
很好理解,String s4 = "11";
在串池中找不到"11"这个字符串对象了。
StringBuilder踩过的坑
你需要先了解
在读这一段时,你需要了解两个东西,也是我踩过的大坑!
StringBuilder的toString方法
能看到,StringBuilder的toString方法其实就是通过new String()去创建一个字符串对象,大家都知道,new String()这个方法会创建两个对象,一个在堆中,一个在非堆中。(上面也介绍了)
如果new String()方法是它们,那么上述说法完全没错
关键是,它调的new String(x,x,x)有三个参数…
该方法只将字符串对象创建到堆中,串池中不会创建!!也就是调用new String(x,x,x)只在堆中创建一个对象。
如何证明?那就调用它看看
如果将value存储串池,肯定会看到一个 idc #指令。
StringBulider append方法
我要说的就是,new StringBuilder()和append()方法都会将字符串存入到常量池中。
如果不会是不可能出现idc的。
知道上面两点,就可以看下面的例子了。
例1
String a = new StringBuilder("北京").toString();
String internA = a.intern();
System.out.println(a == internA); //false
这段代码很简单,看到这里你应该能明白,就不分析了,主要是快12点了,我要睡觉了。
例2
String b = new StringBuilder("计算机").append("软件").toString();
String internB = b.intern();
System.out.println(b == internB); //true
分析:
new StringBuilder("计算机").append("软件").toString()
,先new StringBuilder(“计算机”),此时创建两个对象分别在堆和串池中,.append(“软件”)拼接堆中字符串,现在堆中也就如下图所示,然后将"软件"也放到串池中,注意,再说一遍.toString()不往串池中放东西!此时b引用指向堆中对象。String internB = b.intern()
,将b存入串池中,首页先判断串池是否有"计算机软件"字符串对象,发现没有,这时去堆中看看,发现堆中存在该字符串对象,那就将堆中的这个对象的引用存到串池中,此时innernB指向如图所示。
再看一个特例
String b = new StringBuilder("ja").append("va").toString();
String internB = b.intern();
System.out.println(b == internB); //false
原因就是,java是jvm中的关键字,它本来就在串池中存在。
最后在补充一个知识点 String s3 = s1 + s2底层执行了什么?
看下面一段代码:
public class StringTableTest {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = s1 + s2;
}
}
我们使用javap -v xx.class
,将这段代码对应的字节码文件反编译,看看String s3 = s1 + s2;
这行代码是如何执行的,主要看下面框出来的:
分析:
-
6:new StringBuilder();
-
10:调用StringBuilder的构造方法初始化;
-
14:调用StringBuilder对象的append方法,这里调用了append(s1);
-
18:又调用StringBuilder对象的append方法,也就是append(s2);
-
最后调用了toString()方法返回一个String类型的对象;
所以:
String s1 = "a";
String s2 = "b";
String s0 = "ab";
String s3 = s1 + s2;
System.out.println(s0 == s3); // false