对Java常量池与String 的 intern 方法的理解
intern是什么
String.intern() 是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中代表这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中方,并且返回此String对象的引用。
方法的声明:
public native String intern();
intern的作用:
intern()方法设计的初衷,就是重用String对象,以节省内存消耗。
String的几种创建方式
-
String s = "hello "+"world";//等价于直接赋值"hello world"
这种方式在编译阶段会合成一个字符串
-
String s1="world"; String s = "hello "+s1;
以上代码实际运行过程
String s1="world"; StringBuilder sb=new StringBuilder("hello"); sb.append(s1); String s = sb.toString();
-
String s=new String("hello ") + new String("world");
此种同样是StringBuilder的拼接
使用intern的例子,理解intern原理
例1:
String str1 = new String("coo")+new String("hua");
System.out.println(str1.intern()==str1);
System.out.println(str1=="coohua");
(jdk1.8)运行结果:
true
true
例2:
String st = "coohua";
String str1 = new String("coo")+new String("hua");
System.out.println(str1.intern()==str1);
System.out.println(str1=="coohua");
(jdk1.8)运行结果:
false
false
对比以上两个样例代码,发现只存在一行代码的区别,运行的结果截然不同。这种现象的产生,就是由于intern()方法造成的。在JVM运行时数据区中的方法区有一个常量池,但是在JDK1.6以后,常量池被放置在了堆空间。在JDK1.6,JDK会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中的这个字符串实例的引用。而JDK1.7及以后的intern不再复制实例,只是在常量池中记录首次出现的实例引用。
String可通过字面量赋值,也可以通过new String() 创建对象。 通过字面量赋值创建字符串时,会先在常量池中查找是否已经存在相同的字符串,倘若已经存在,栈中的引用直接指向该字符串;倘若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串。而通过new的方式创建字符串时,就直接在堆中生成一个字符串的对象,栈中的引用指向该对象。
图示:
知识点备案
常量池
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
静态常量池指的时候 *.class 文件中的常量池, *.class 文件中的常量池不仅仅包含字符串(数字)的字面量,还包含类、方法的信息。
运行时常量池则是在jvm虚拟机完成类装载操作后,将 class 文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池(jdk1.6及之前),jdk1.6以及之后常量池被移到了堆空间。
常量池的好处
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用判断引用是否相等,也就可以判断 实际值是否相等。
注意:
JDK1.7开始,常量池由方法区(永久代)迁移至堆空间。对intern产生的影响:
JKD1.6及之前:
如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。
JDK1.6之后:
当常量池中没有该字符串时,JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录java Heap中首次出现的该字符串的引用,并返回该引用。