Java学习

java中的String和String常量池

2018-12-31  本文已影响92人  唐T唐X

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

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”,所以结果相等。

上一篇下一篇

猜你喜欢

热点阅读