详解String拼接字符串,哈希值一样 为什么==的结果是false?

详解String拼接字符串,哈希值一样 为什么==的结果是false?,第1张

详解String拼接字符串,哈希值一样 为什么==的结果是false? String类型(本文是从反编译的代码查看原因)

以下代码输出为多少?

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();

        System.out.println("3,4   " + (s3 == s4));
        System.out.println("3,5   " + (s3 == s5));
        System.out.println("3,6   " + (s3 == s6));
        System.out.println("4,5   " + (s4 == s5));
        System.out.println("4,6   " + (s4 == s6));
        System.out.println("5,6   " + (s5 == s6));


    }

解析:
我们先来看s3,s4,s5这三个字符串,在看这道题之前我们先来一个个看每个字符串是怎么诞生的。
先看看最简单的s5

先了解一下常量池跟串池的关系
常量池:最初存在于字节码文件中,运行时,加载到运行时常量池,此时定义的常量,如a,b,ab,还只是一些符号,还没有变为java中的字符串对象
1. 当执行到 ldc #2 //String a 这一行的时候。会把a符号变为"a"字符串对象,并且开辟一个新的空间:StringTable[],这个就是串池,然后在串池中寻找有没有"b"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”]
2. 当执行到 ldc #3 //String b 这一行的时候。会把b符号变为"b"字符串对象,然后在串池中寻找有没有"a"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”,“b”]
3. 当执行到 ldc #4 //String ab 这一行的时候。会把ab符号变为"ab"字符串对象,然后在串池中寻找有没有"ab"字符串,没有的就添加进去,有就不添加,此时串池:StringTable[“a”,“b”,“ab”]

    s3:
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s5 = "ab";
        String s3 = "a" + "b";
    }

使用javap -v 反编译来查看

从反编译文件中我们能看出来,jvm在执行到9:的时候已经将s1,s2,s5的"a",“b”,“ab"传入到串池中,比较时都是引用串池中的"ab”,所以s3==s5为true。

    s4:
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s4 = s1 + s2;
    }


由图我们可以看出来先调用了new StringBuilder()方法,补充(astore_1是将其存入局部变量表中,aload_1是将其拿出),在13:中拿到s1这个参数,在14:使用append方法将其传入,在17:加载了s2这个参数,在18:使用append方法将其传入,最后使用toString方法拼接字符串。

 String s4 = s1 + s2;//这一行代码可以看成
 String s4_new = new StringBuilder().append("a").append("b").toSring(); 

而我们通过以下toString方法的源代码,可以看出其是new了一个新的String;

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

一旦new了新的字符串来开辟新的空间所以其地址值不一样,一旦new对象之后,地址值都会变

当我们比较s3和s4的时候,s3的"ab"是在串池中的"ab",而s4引用的是一个新的字符串对象,虽然值一样,但是s3中的"ab"是在串池中的,而s4的"ab"是在堆中的对象,所以当我们打印s3==s4时,结果为false

也可以看下图,步骤更为清晰

    在讲s6之前我们要先将StringTable(串池)的特性和intern方法
      常量池中的字符串仅仅是符号,第一次用到时才变为对象字符串变量拼接的原理是StringBuilder(1.8)字符串常量拼接的原理是编译器优化可以使用intern方法,主从将串池中还没有的字符串对象放入串池
      举个栗子:
        String s1 = new String("a") + new String("b");
        String s2 =  s1.intern();
        System.out.println(x2 == "ab");

通过上面的学习
当第一行行代码执行结束后,“a”,“b"被放入串池中
但是 new String(“a”)和new String(“b”)因为是new出来的,所以是在堆当中,与常量池中的不一样,还要一个new String(“ab”),但是由于它是动态拼接,所以还没有进入串池中
第二行的s2.intern();意思就是尝试将s2这个字符串放如串池中,如果有则不会放入,没有就放入,会把串池中的对象返回,此时"a”,“b”,"ab"均被放入串池中
此时的s2引用的对象就是串池中的"ab"
第三行中比较的均为串池中的对象所以结果为true

补充:以上intern方法是在jdk1.8为基础的前提下执行的,那么在jdk1.6有什么区别呢?

1.8:将这个字符串对象尝试放入串池,如果有则不放入,如果没有则会放入串池,会把串池中的对象返回

1.6:将这个字符串对象尝试放入串池,如果有则不放入,如果没有则会复制一份放入串池,会把串池中的对象返回
那么有什么区别呢?

我们在两个环境下执行以下相同代码

    public static void main(String[] args) {
        String x = "ab";
        String s = new String("a") + new String("b");
        String s2 = s.intern();
        System.out.println(s == x);
        System.out.println(s2 == x);
    }

我们先分析以下:
第一行执行结束,串池中有:“ab”
第二行执行结束,串池中有:“ab”,“a”,“b”,堆中有 new String(“a”) new String(“b”) new String(“ab”)
第三行执行结束,串池中有:“ab”,“a”,“b”,尝试将s放入串池,但是串池中已经有了"ab",所以s2引用的就是串池中的"ab"
所以:s是堆中的new String(“ab”),s2则是串池中的"ab"。

    //StringTable:["a","b","ab"]
    public static void main(String[] args) {
        String x = "ab";
        String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab")
        String s2 = s.intern();//s-->堆中的new String("ab"),s2-->StringTable:["a","b","ab"]中的ab
        System.out.println(s == x);//false
        System.out.println(s2 == x);//true
    }

当我们把String x = “ab”;放到第三行
这是1.8环境下运行的结果

    //StringTable:["a","b","ab"]
    public static void main(String[] args) {
        String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab")
        String s2 = s.intern();//堆new String("a") new String("b") s-->new String("ab") s2-->StringTable:["a","b","ab"]的ab
        String x = "ab";//x-->StringTable:["a","b","ab"]的ab
        System.out.println(s == x);//true
        System.out.println(s2 == x);//true
    }

分析:
第一行执行完,我们在串池中加入了"a",“b”,堆中加入了new String(“a”) new String(“b”) new String(“ab”)
第二行执行完,尝试着吧"ab"放入串池,这个时候串池中没有"ab",所以成功放入了,这个时候堆中有new String(“a”) new String(“b”), s–>new String(“ab”) s2–>StringTable:[“a”,“b”,“ab”]的ab,(我用指针代表的是引用的对象)
第三行,因为串池中有"ab",所以直接引用串池中的"ab",x–>StringTable:[“a”,“b”,“ab”]的ab

这是1.6环境下运行的结果

    //StringTable:["a","b","ab"]
    public static void main(String[] args) {
        String s = new String("a") + new String("b");//堆 new String("a") new String("b") new String("ab")
        String s2 = s.intern();//堆new String("a") new String("b") s-->new String("ab") s2-->StringTable:["a","b","ab"]的ab
        String x = "ab";//x-->StringTable:["a","b","ab"]的ab
        System.out.println(s == x);//false
        System.out.println(s2 == x);//true
    }

最后解答一开始的问题:

    //StringTable["a","b","ab"]
    public static void main(String[] args) {
        String s1 = "a";//将"a"放入串池
        String s2 = "b";//将"b"放入串池
        String s3 = "a" + "b";//将"ab"放入串池,s3->StringTable["a","b","ab"]中的ab
        String s4 = s1 + s2;//堆中 s4->new String("ab")
        String s5 = "ab";//串池中有"ab",所以,s5->StringTable["a","b","ab"]中的ab
        String s6 = s4.intern();//因为StringTable["a","b","ab"]有"ab",所以s4->new String("ab") s6->StringTable["a","b","ab"]中的ab


        System.out.println("3,4   " + (s3 == s4));//false
        System.out.println("3,5   " + (s3 == s5));//true
        System.out.println("3,6   " + (s3 == s6));//true
        System.out.println("4,5   " + (s4 == s5));//false
        System.out.println("4,6   " + (s4 == s6));//false
        System.out.println("5,6   " + (s5 == s6));//true
        }

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/zaji/5706994.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存