StringBuilder在常量池及堆空间中的操作

转 原文链接:https://blog.csdn.net/qq_44919287/article/details/113754892

下列代码输出的结果是什么?

String str = new StringBuilder("ss").append("ss").toString();
System.out.println(str == str.intern());
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1 == str1.intern());

结果是:一个true加一个false

问题很简单,解释如下:
“ss"和"ss"拼接成"ssss”,变量str指向堆中的"ssss",常量池中是没有"ssss"的。
  intern()方法在常量池中的进行如下操作,如果有"ssss"就返回已有"ssss"的地址值,如果没有就在常量池中创建一个"ssss"并返回。这个时候就有人要问了一个指向堆空间,一个指向常量池,不应该返回false吗。这样想是对的,不过是在jdk7之前是对的,jdk7开始intern()方法逻辑变了。如果常量池中没有"ssss",不是立即创建,而是看看堆中有没有,如果有就直接用堆中的数据。所以两个引用指向的都是堆中的"ssss",返回true。


“ja"和"va"拼接成"java”,s1指向的是堆中的"java",和上面一样,本应该不在常量池中创建,直接使用堆中的"java",但"java"这个字符串很特殊。他是jvm自带的字符串。所以常量池中是有"java"字符串的,所以直接使用常量池中数据。两个引用指向不同返回false。
复现代码时发现的问题

为了不出现”脑子会了,手不会"的局面,我在idea上手写了一下,但写着写着发现情况不对。

String s = new StringBuilder("ss").append("ss").toString();
System.out.println(s == s.intern());

结果为true

String s1 = new StringBuilder("ssss").toString();
System.out.println(s1 == s1.intern());

结果为false

提示:这两串代码是在不同的类中写的,写在同一个类会有干扰
  为什么会这样呢?上面比下面就多了一个append()方法结果就反了,append无非是拼接字符串,没什么特殊操作。况且如果看一下StringBuilder的构造方法,他的底层也是append()来实现的。

public StringBuilder(String str) {
    super(str.length() + 16);
    append(str);
}

        这是什么情况呢?要搞清楚这个问题,必须先搞清楚String的创建过程。正常情况下创建String的方式主要是一下两种方法:

String s = "sss";
Stirng s1 = new String("sss");

         都是简单常用的方式,第一种直接在常量池中创建"sss"并返回,第二种是在堆中创建一个"sss",然后看看常量池中有没有"sss",没有也给创建一个,但是返回的是堆中的对象地址。
  如果你了解上述过程(大部分人都了解),又不巧对StringBuilder的源码了解不多,那么只能恭喜你,StringBuilder会把你坑的很惨。


  回到正题,既然结果不同是事实,最简单的方法就是逆向推导。jdk7之前intern()方法一大作用就是保证了引用一定是指向常量池的。但变动之后这一功能直接没了,所以==判断的是堆池,还是堆堆,无法确定。
  那只能一步一步分析了,无论使没使用append()都不影响,因为new StringBuilder()底层调用的就是append(),所以问题只能在这几个常量上。new StringBuilser("…")和append("…")都会在常量池中创建对应的字符串,这个看字节码就能看出来。ldc拿取,一定是存在的字符串。
  也就是说,现在常量池中一定有"ss"字符串,两个ss拼接变成ssss在堆中,然后调用toString()方法,toString()的底层是new String(),上面讲过new String()会创建一个或两个对象,所以。。。。。。一套逻辑下来发现不对啊,堆中常量池中因为new String的存在一定都有ssss啊,应该是false啊。
  这里就要解释一下原始题目隐藏的知识点了,再放一遍原始题目:

String str = new StringBuilder("ss").append("ss").toString();
System.out.println(str == str.intern());
String str1 = new StringBuilder("ja").append("va").toString();
System.out.println(str1 == str1.intern());

        为什么这里的常量池没有"ssss"对象呢?不是调用toString方法了吗,不就相当于调用了new String吗?应该有对象才对啊。对,应该有,但是他就是没有。答案只有一个,这个new String有鬼,看一下源码,调用的不是new String("…"),而是new String(char value[], int offset, int count)。也就是说这个三个参数的new String没有在常量池中创建对象,因为他底层只是复制了一下。详细的推理,我不会,源码我也看不懂,我只是个菜鸡。但我可以复现啊

String s = new String(new char[]{'a','b'},0,2);

翻看对应的字节码常量池,没有"ab"。
  现在答案已经清晰明了了,后面的两个之所以一个为true一个为false。因为第一段代码的常量池中只有"ss",intern()也不能创建,new String(…,…,…)也不会创建,所以都调用的是堆中的;第二段代码因为new StringBuilder(“ssss”)直接在常量池中创建了一个"ssss"所以不必调用堆中对象,为false。


  这是本人的第一篇博客,可能废话有点多,不会插入图片,更不会作图,如果觉得讲的不够直观,还请见谅。

  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值