美文网首页
不同 JDK 的 String.Intern ( ) 的不同之处

不同 JDK 的 String.Intern ( ) 的不同之处

作者: 得瑟的小蚂蚁 | 来源:发表于2018-11-21 11:49 被阅读28次

    最近在读 《深入理解java虚拟机》,总结一下 String.intern 知识点,引入书中的一个题目:

        public class RuntimeConstantPoolOOM {
                public static void main(String[] args){
                    String str1 = new StringBuilder("计算机").append("软件").toString();
                    System.out.println(str1.intern() == str1);
    
                    String str2 = new StringBuilder("ja").append("va").toString();
                    System.out.println(str2.intern() == str2);
    
                  }
    
            }
    

    这段代码在JDK 1.6中运行,会得到两个false,而在JDK1.7中运行,会得到一个true和一个false。产生差异的原因是:在JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而StringBuildler创建的字符串实例在Java堆上,所以必然不是一个引用,将返回false。而JDK1.7中(以及部分其他虚拟机,例如JRockit)的intern()实现不会再 复制实例,只是在常量池中记录首次出现实例的引用,因此intern()返回的引用和由StringBuilder创建的字符串实例是同一个;对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有它的引用,不符合首次出现的规则,而"计算机软件"这个字符串则是首次出现的,因为返回true;

    下面引入几幅图对 intern 方法作如下总结:
    • new String 都是在堆上创建字符串对象。当调用 intern() 方法时,JDK1.6中编译器会将字符串添加到常量池中,并返回指向该常量的引用。
    image image
    • 通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。
    image
    • 常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用。
    • 对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)。
        final String str1 = ”ja” ;
    
        final String str2 = ”va” ;
    
        String str3 = str1+str2 ;
    

    在编译时,直接替换成了 String str3 = ”ja” + ”va”,根据第三条规则,再次替换成 String str3=”JAVA” 。

    • 常量字符串和变量拼接时(如:String str3 = baseStr + “01” ;)会调用 stringBuilder.append() 在堆上创建新的对象。
    • JDK 1.7 后,intern 方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。

    另外参考 关于String.intern()和new StringBuilder("").append("").toString();

    先运行这个代码 ①

        String str3 = new StringBuilder("ni").append("hao").toString();
    
        System.out.println(str3==str3.intern());
    

    通过上面的解释,运行结果为true.

    在运行这个代码 ②

        String str3 = new StringBuilder("nihao").toString();
    
        System.out.println(str3==str3.intern());
    

    其结果是什么 ? 应该还是 true 吧 ,毕竟通过上一个运行结果可以知道 "nihao" 这个字符串常量没有被预先加载到常量池中 。
    但是运行结果却是 false .

    解释如下 :上面的 ① 代码等价于下面的代码

        String a = "ni";
    
        String b = "hao";
        
        String str3 = new StringBuilder(a).append(b).toString();
        
        System.out.println(str3==str3.intern());
    

    很容易分析出 :

    “nihao” 最先创建在堆中 str3.intern() 然后缓存在字符串常连池中 运行结果为 true .

    代码 ② 等价于

        String a = "nihao";
    
        String str3 = new StringBuilder(a).toString();
        
        System.out.println(str3==str3.intern());
    

    很容易分析出:

    “nihao” 最先创建在常量池中, 运行结果为false.

    new String()和new StringBuilder()同样的原理,由此对于一道 经典的Java面试题

    在Java中,new String("hello")这样的创建方式,到底创建了几个String对象 ?

    题目下答案众说纷纭,有说1的有说2的,我觉得各有各的道理,如果常量池中有“hello”的字符串,当然只会创建1个,如果没有则会创建2个;

    new String ("hello")相当于如下代码:

        String temp = "hello";  // 在常量池中
    
        String str = new String(temp); // 在堆上
    

    相关文章

      网友评论

          本文标题:不同 JDK 的 String.Intern ( ) 的不同之处

          本文链接:https://www.haomeiwen.com/subject/uqgqqqtx.html