String池化及intern()方法的作用

声明:以下内容可能有误

常量池

要理解String的池化,必须首先知道常量池这个概念。

  • 方法区:存储Class文件编译后的类信息、常量、静态变量、编译后的代码

  • 常量池:存储Class文件编译后的字面量和符号引用;但这个常量池又叫运行时常量池,则说明可以在程序运行期间将新的常量放入池中,如使用String类的intern()。那么为什么要使用常量池:通过对象共享的方式避免频繁的创建和销毁对象,从而节省空间,且节省运行时间(比如比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等)。

通俗的来说,Class文件中除常量表(constant pool table)之外的信息都会加载到方法区,而常量表中的信息都会加载到常量池中。

在JDK1.6及之前,常量池位于方法区中,而在JDK1.7及之后,常量池位于堆中。

String

String是不可变类,即一旦创建,就不能再次修改这个对象。而在编程中,String类型是最长用到的数据类型,对字符常量池化后,可以大大减少字符的数量,从而节省空间。

比如,通过如下方式声明两个String类型的变量,只会在内存中(常量池中)创建一个“abc”这个字符串,而s1和s2均会指向这个地址,所以,无论使用s1==s2还是s1.equals(s2),都会得到true的结果。

String s1 = "abc";
String s2 = "abc";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
//输出结果
true
true

但是,如果使用new关键字来创建String对象,情况则会不同:

String s1 = new String("abc");    ------
String s2 = new String("abc");    ------
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
//输出结果
fasle
true

对于new String("abc")这句代码需要这么来理解:在类加载的时候,就会将“abc”放在常量池中;在代码真正执行的时候,会从常量池中复制“abc”到堆中或者说在堆中创建“abc”这个字面量(如下面的字节码所示)。通过new这种方式创建String对象,与使用new创建其他普通对象是没有区别的,上述代码中的①和②均会在堆中创建对象,只不过这两个对象的内容都是“abc”,但内存地址不一样,所以,使用==来比较,结果为false。

0: new  #2; //class java/lang/String  
3: dup  
4: ldc  #3; //String abc
6: invokespecial    #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V  
9: astore_1

前面说到,可以在运行期间动态地向常量池中添加常量,如果想要在运行期间向运行池中添加一个字符串,则可以使用String类中提供的一个方法:intern()intern这个单词是“驻留”的意思,也就是将某个字符串放置到常量池中。但是,如何放,不同JDK版本是不一样的(因为常量池的位置发生了变化)。

  • JDK1.6及之前:如果常量池中已经存在想要放入的字符串(equals),则直接返回已经存在的那个字符串的地址,如果不存在,则将其放入,并返回引用地址。

  • JDK1.7及之后:如果常量池中不存在,但堆中存在,则直接在常量池中存储这个字符串在堆中的地址,而不是像JDK1.6那样存储这个字符串本身。

即通过intern()方法,可以确保从常量池中得到与想要放入的字符串值(假设为s)equals的一个字符串的地址(假设为s_addr),并且只要s是equals的,那么得到的永远是这个s_addr。

使用intern的场景:需要使用new String(String s) 大量创建String的对象的时候,使用new String(s.intern()) 来替代,可以节省内存空间。

TODO

关于intern()还有没能理解的地方,比如在JDK1.8中:执行以下代码,输出结果为fasle和true。

String s = new StringBuilder("ja").append("va").toString();
System.out.println(s.intern() == s);

String s1 = new StringBuilder("abc").append("abc").toString();
System.out.println(s1.intern() == s1);

参考

请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧

Java常量池理解与总结

String池化及intern方法的作用

深入解析String#intern

Java技术——你真的了解String类的intern()方法吗

Last updated