从字符串到常量池,一文看懂String类设计 (5)

就我查阅的资料而言,我个人不赞同这种说法,常量池中应该保存的仅仅是引用。关于这个问题,我已经向美团的团队进行了留言,也请大佬出来纠错!

接着我们分析s3跟s4,对应的就是这几行代码:

String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4);

我们一行行分析,看看执行完后,内存的状态是什么样的

第一步:String s3 = new String("1") + new String("1"),执行完成后,堆区多了两个匿名对象,这个我们不用多关注,另外堆区还多了一个字面量为11的字符串实例,并且栈中存在一个引用指向这个实例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NVeeWKoO-1592334452491)(upload\image-20200617020742618.png)]

实际上上图中还少了一个匿名的StringBuilder的对象,这是因为当我们在进行字符串拼接时,编译器默认会创建一个StringBuilder对象并调用其append方法来进行拼接,最后再调用其toString方法来转换成一个字符串,StringBuilder的toString方法其实就是new一个字符串

public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }

这也是为什么在图中会说在堆上多了一个字面量为11的字符串实例的原因,因为实际上就是new出来的嘛!

第二步:s3.intern()

调用intern方法后,因为字符串常量池中目前没有11这个字面量对应的字符串实例的应用,所以JVM会先从堆区复制一个字符串实例到永久代中,再将其引用添加到字符串常量池中,最终的内存状态就如下所示

image-20200617021429393

第三步:String s4 = "11"

这应该没啥好说的了吧,常量池中有了,直接指向对应的字符串实例

image-20200617022337592

到这里可以发现,s3跟s4指向的根本就是两个不同的对象,所以也返回false

jdk7 执行流程

在jdk1.7中,s跟s2的执行结果还是一样的,这是因为 String s = new String("1")这行代码本身就创建了两个字符串对象,一个属于被常量池引用的驻留字符串,而另外一个只是堆上的一个普通字符串对象。跟1.6的区别在于,1.7中的驻留字符串位于堆上,而1.6中的位于方法区中,但是本质上它们还是两个不同的对象,在下面代码执行完后

String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2);

内存状态为:

image-20200617023304860

但是对于s3跟s4确不同了,因为在jdk1.7中不会再去复制字符串实例了,在intern方法执行时在发现堆上有对应的对象之后,直接将这个对应的引用添加到字符串常量池中,所以代码执行完,内存状态对应如下:

image-20200617023832783

看到了吧,s3跟s4指向的同一个对象,这是因为intern方法执行时,直接s3这个引用复制到了常量池,之后执行String s4= "11"的时候,直接再将常量池中的引用复制给了s4,所以s3==s4肯定为true啦。

在理解了它们之间的差异之后,我们再来思考一个问题,假设我现在将代码改成这个样子,那么运行结果是什么样的呢?

public static void main(String[] args) { String s = new String("1"); String sintern = s.intern(); String s2 = "1"; System.out.println(sintern == s2); String s3 = new String("1") + new String("1"); String s3intern = s3.intern(); String s4 = "11"; System.out.println(s3intern == s4); }

上面这段代码运行起来结果会有差异吗?大家可以自行思考~

在我们对字符串常量池有了一定理解之后会发现,其实通过String name = "dmz"这行代码申明一个字符串,实际的执行逻辑就像下面这段伪代码所示

/** * 这段代码逻辑类比于 * <code>String s = "字面量"</code>;这种方式申明一个字符串 * 其中字面量就是在""中的值 * */ public String declareString(字面量) { String s; // 这是一个伪方法,标明会根据字面量的值到字符串值中查找是否存在对应String实例的引用 s = findInStringTable(字面量); // 说明字符串池中已经存在了这个引用,那么直接返回 if (s != null) { return s; } // 不存在这个引用,需要新建一个字符串实例,然后调用其intern方法将其拘留到字符串池中, // 最后返回这个新建字符串的引用 s = new String(字面量); // 调用intern方法,将创建好的字符串放入到StringTable中, // 类似就是调用StringTable.add(s)这也的一个伪方法 s.intern(); return s; }

按照这个逻辑,我们将我们将上面思考题中的所有字面量进行替换,会发现不管在哪个版本中结果都应该返回true。

运行时常量池 位置在哪?

位于方法区中,1.6在永久代,1.7在元空间中,永久代跟元空间都是对方法区的实现

用来干什么?

jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证# 位置在哪?

位于方法区中,1.6在永久代,1.7在元空间中,永久代跟元空间都是对方法区的实现

用来干什么?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypdgs.html