对intern方法的学习来源于之前偶然看到的一篇博客:【请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧】
博客地址:https://www.iteye.com/topic/774673
这篇博客写的很好,从各方面详细的讲解了Java底层的一些东西,不搞底层的看着可能会觉得很涩,很难懂,有兴趣的可以看看,当然本篇重点不在这边,而是对intern方法的使用方面的解析
在JDK1.6版本及以前,字符串常量池是存放在永久代中的,当使用intern方法的时候,会先查询字符串常量池中是否存在当前的字符串,如果不存在的话,就会将当前的字符串复制到字符串常量池中,并返回字符串常量池的引用。
JDK1.7以后,字符串常量池是存放在堆中的,当使用intern方法的时候,也会先查询字符串常量池中是否存在当前的字符串,如果不存在,再从堆中查询,然后存储并返回相关引用。如果都不存在的话,就会将当前的字符串复制到字符串常量池中,并返回字符串常量池的引用。
借用美团技术团队《深入解析String#intern》一文中两段代码与相关图片来进行解释:
第一段代码:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
JDK1.6版本的运行结果为false false
解析:
String s = new String("1"),在字符串常量池中创建了“1”对象,在堆中创建了s对象。
s.intern(),因为字符串常量池已经有了“1”对象,所以可以跳过。
String s2 = "1",由于字符串常量池中已经存在“1”对象,所以s2直接指向字符串常量池中的“1”对象。
所以,s是堆中对象的引用,而s2指向的是字符串常量池,结果为false。
String s3 = new String("1") + new String("1"),在堆中创建了“1”对象,在字符串常量池中创建了s3对象。
s3.intern(),是直接将“11”复制到字符串常量池中。
String s4 = "11",指向了字符串常量池中的“11”。
所以,s3是堆中的引用,而s4指向的是字符串常量池,结果为false。
JDK1.7版本的结果为false true
解析:
s和s2的结果为false的原因同上。
而s3.intern(),返回的是堆中的引用,所以s4指向的其实也是堆中的引用,所以结果为true。
第二段代码:
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
这里将s2、s4对象的创建和intern方法的使用交换了一下顺序。
JDK1.6版本的运行结果为false false
解析:
上半段代码先在堆中创建了s对象,在字符串常量池创建了“1”对象,s2指向字符串常量池的“1”,这里的intern也就没有了实际的意义。所以一个指向堆中的引用,一个指向字符串常量池,false。
下半段代码是先在堆中创建了s3对象,在字符串常量池创建了“1”对象,又在字符串常量池中创建了“11”对象,所以,s3指向堆中的引用,s4指向字符串常量池,false。
JDK1.7版本的运行结果同样为false false
原理和1.6版本的解析基本一致,都是false,不是true,是因为上面的为true的是intern先执行,会存储堆中的引用,而这里是先执行的是String s4 = "11",在查询之前就重新创建了一个“11”对象,所以s3和s4指向的不是同一个,执行结果为false。
第三段代码:
static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];
public static void main(String[] args) throws Exception {
Integer[] DB_DATA = new Integer[10];
Random random = new Random(10 * 10000);
for (int i = 0; i < DB_DATA.length; i++) {
DB_DATA[i] = random.nextInt();
}
long t = System.currentTimeMillis();
for (int i = 0; i < MAX; i++) {
arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
//arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
}
System.out.println((System.currentTimeMillis() - t) + "ms");
System.gc();
}
使用intern的结果为2044ms,不适用intern的结果为5016ms
总结一下:intern方法返回的是字符串对象的规范化表示形式,初衷就是为了让string对象能够被重复的使用,以此来节省内存的损耗。更深层次的对intern的解析可以参照:
美团技术团队《深入解析String#intern》 https://tech.meituan.com/in_depth_understanding_string_intern.html