美文网首页Java学习
java中的String和String常量池

java中的String和String常量池

作者: 唐T唐X | 来源:发表于2018-12-31 16:35 被阅读92次

    在描述理论之前,我们先看段代码:

    1   public static void main(String args[]) {
    2
    3       String s1 = "test";
    4       String s2 = "test";
    5       String s3 = "te" + "st";
    6       String s4 = new String("test");
    7       String s5 = s4.intern();
    8       String s6 = "te";
    9       String s7 = "st";
    10      String s8 = s6 + s7;
    11
    12      System.out.println(s1 == s2);  // true
    13      System.out.println(s1 == s3);  // true
    14      System.out.println(s1 == s4);  // false
    15      System.out.println(s1 == s5);  // true
    16      System.out.println(s1 == s8);  // false
    17      System.out.println(s1.equals(s8));  // true
    18  }
    

    各位读者可以先按照自己的理解想一下代码中等式的结果,看看最后的结果是不是一样。如果一样,那么恭喜您,您对JVM中运行时数据区,尤其是方法区里面的运行时常量池的理解已经相当透彻,就可以忽略这篇文章啦;如果有点出入,那我们就值得一起来看看这是怎么回事儿。

    如果想要知道上面的代码结果的成因,我们起码需要先知道一些名词:Java堆、方法区、运行时常量池

    Java堆

    JVM运行时数据区的一部分。用于存放对象实例,几乎所有的对象都在堆上分配内存。

    方法区

    JVM运行时数据区的一部分。方法区用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    运行时常量池

    方法区的一部分,class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    其实看完了这三个名词,我们还是不很了解和程序中的结果有什么关系。别急,我们这就开始慢慢表~

    第12行结果:

    s1和s2的比对结果是true,原因就是上面运行时常量池中说到的其作用:

    用于存放编译器生成的各种字面量
    用于存放编译器生成的各种字面量
    用于存放编译器生成的各种字面量

    重要的事情说三遍,因为本文中大部分的解释都和这句话有关。s1、s2在赋值时,均使用的字符串字面量。这种字面量会直接放入class文件的常量池中,从而实现复用。编译载入运行时常量池后,s1、s2指向的是同一个内存地址,所以结果为true。
    从这里我们也能得到一个重要信息,运行时常量池存在的原因之一就是节省内存。

    第13行结果:

    s1和s3的比对结果是true。这里不得不说下编译的事儿:s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = "te" + "st" 在class文件中被优化成String s3 = "test"。加载到运行时常量池后和s1、s2还是只想同一个内存地址。

    第14行结果:

    这里面就牵扯到java堆的概念了。s4使用new来显示的创建对象实例是在堆中分配的,而s1是在方法区的常量池中,地址一定不同,所以结果为false

    第15行结果:

    String.intern()这个方法会尝试将test字符串添加到常量池中,并返回其在常量池中的地址;因为常量池中已经有了test字符串(s1、s2、s3都指向),所以intern方法直接返回地址。地址相同,结果为true

    第16行结果:

    s8是由s6和s7拼接而成的,但是对于这种情况,编译器在编译的时候并不能像第5行那样直接帮忙拼好再存入class静态常量池,因为编译器还没有智能到认为“s6和s7就是字面量,所以s8也是字面量”这种程度(虽然我认为也不是很难的事儿),所以在编译后载入运行时常量池的时候也就不可能使用相同的地址。所以结果为false

    第17行结果:

    上面所有的比对都是比对引用地址,而这句是比对内容。而s1和s8的内容都是“test”,所以结果相等。

    相关文章

      网友评论

        本文标题:java中的String和String常量池

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