关于对字符串常量池深入理解的探究

目录

1.问题表述

2.问题所需知识讲解

3.问题探究

4.问题分析


1.问题表述

        写这篇文章是我在刷题的时候遇到了这么一道题:

public class TestDemo {
    public static void main(String[] args) {
        //代码1
        String z1 = "zhangsan";
        String z2 = new String("zhangsan");
        System.out.println("代码1:"+ (z1 == z2));

        //代码2
        String z3 = new String("lisi");
        System.out.println("代码2:" + (z3.intern() == z3));
        //代码3
        String s1 = new StringBuilder("wangwu").toString();
        System.out.println("代码3:" + (s1.intern() == s1));
        //代码4
        String s2 = new StringBuilder("ma").append("liu").toString();
        String intern = s2.intern();
        System.out.println("代码4:"+ (intern == s2));
    }
}

我们看这个代码,它的打印结果是:

这里我就有一个疑问:为什么代码4是true。

2.问题所需知识讲解

字符串常量池设计原理
字符串常量池底层是hotspot的C++实现的,底层类似一个 HashTable, 保存的本质上是字符串对象的引用。
看一道比较常见的面试题,下面的代码创建了多少个 String 对象?

为什么输出会有这些变化呢?主要还是字符串池从永久代中脱离、移入堆区的原因, intern() 方法也相应发生了变
化:
1、在 JDK 1.6 中,调用 intern() 首先会在字符串池中寻找 equal() 相等的字符串,假如字符串存在就返回该字符串在字
符串池中的引用;假如字符串不存在,虚拟机会重新在永久代上创建一个实例,将 StringTable 的一个表项指向这个新
创建的实例。
2、在 JDK 1.7 (及以上版本)中,由于字符串池不在永久代了,intern() 做了一些修改,更方便地利用堆中的对象。字符
串存在时和 JDK 1.6一样,但是字符串不存在时不再需要重新创建实例,可以直接指向堆上的实例。

3.问题探究

我们继续来看上面的代码,我是jdk1.8的环境,代码1. z1直接赋值会在常量池中创建zhangsan,但是new String会在堆中创建zhangsan,所以他们的引用地址不一样,所以==比较为false;

代码2. new String("lisi") 会在堆中创建对象,也会在常量池中创建lisi,所以intern拿到常量池中对象进行==比较也会是false。

那么问题来了,代码3和代码4结果为什么不一样呢,他们都是通过StringBuilder创建的对象,

我看了StringBuilder的源码,将他们拆分成了源码级别的代码

package synchronizedTest;

public class TestDemo2 {
    public static void main(String[] args) {
        //代码1:
        String z1 = "zhangsan";
        int count = 0;
        char[] test = new char[16];
        z1.getChars(0,z1.length(),test,count);
        count += z1.length();
        String s1 = new String(test,0, count);
        String s2 = s1.intern();
        System.out.println(s2 == s1);


        //代码2
        String str = "li";
        int count1 = 0;
        char[] test1 = new char[16];
        str.getChars(0,str.length(),test1,count1);
        count1 += str.length();
        String str1 = "si";
        str1.getChars(0,str1.length(),test1, count1);
        count1+=str1.length();
        String s3 = new String(test1, 0, count1);
        String s4 = s3.intern();
        System.out.println(s3 == s4);

    }
}

我们看他们的结果确实不一样:

 

4.问题分析

看了源码以后,我猜想new String(value,offset,count) 这个方法只会在堆中创建对象,不会在常量池中创建对象,如果是这样的话就可以解释清楚了

代码3:我在执行:

String s1 = new StringBuilder("wangwu").toString();

的时候,相当于执行:

String test = "wangwu";

String s1 = new StringBuilder(test).toString();

所以会先在常量池中创建wangwu这个对象

然后执行toString()方法,底层执行new String(value,offset,count)这个方法在堆中创建wangwu这个对象

所以调用intern()方法 == 比较为 false

代码4:

和在代码3一样,也会现在常量池中创建“ma”"liu"这两个对象,但是不会创建“maliu”这个对象,然后toString()方法会在堆中创建“maliu”这个对象,调用inter()方法会将堆中"maliu"这个对象的引用地址指向常量池中,所以==比较为true

为了验证我的猜想,我改造一下代码3:我用“zhangsan”这个字符串生成char[]数组,然后生成字符串的时候故意使用5个长度,这样就生成了“zhang”这个字符串,然后调用intern()方法进行比较,果然就变成了true; 验证成功,完美!!!!!

package synchronizedTest;

public class TestDemo2 {
    public static void main(String[] args) {
        //代码1:
        String z1 = "zhangsan";
        int count = 0;
        char[] test = new char[16];
        z1.getChars(0,z1.length(),test,count);
//        count += z1.length();
        count = 5;
        String s1 = new String(test,0, count);
        String s2 = s1.intern();
        System.out.println(s2 == s1);


        //代码2
//        String str = "li";
//        int count1 = 0;
//        char[] test1 = new char[16];
//        str.getChars(0,str.length(),test1,count1);
//        count1 += str.length();
//        String str1 = "si";
//        str1.getChars(0,str1.length(),test1, count1);
//        count1+=str1.length();
//        String s3 = new String(test1, 0, count1);
//        String s4 = s3.intern();
//        System.out.println(s3 == s4);

    }
}

执行结果:

 以上观点仅限于个人想法,如果有误请大佬们指正!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值