【辨析】String.intern()分析

关于String.intern()

最近在看《深入理解JAVA虚拟机》时,碰到一个String.intern()方法,在看这个方法的过程中,踩了一些坑,今天就整理在这里。

先看看jdk7官方文档是怎么说的

1
2
3
4
5
6
7
8
9
10
11
12
public String intern()
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.
All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10.5 of the The Java™ Language Specification.
Returns:
a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.

大体意思就是如果常量池里面有equal方法为true的,就返回那个对象,否则将这个字符串放入常量池中,并返回。

jdk6和jdk7对这个方法实现的差异

先来看看这段代码

1
2
3
4
5
6
7
8
// sec1
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
// sec2
String str2 = new StringBUilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);

结果

1
2
3
4
5
6
7
8
9
10
// jdk6下
false // sec1
false // sec2
// jdk7下
true // sec1
false // sec2

==为啥sec1的结果变化了?==

==为啥sec2的结果没有变化?==

String.intern()是个native方法,底层由C++实现。jdk6中常量池放在Perm空间中,实际上是一个类似于HashTable的结构。在调用intern()方法时,首先检查常量池中有没有eq为true的,如果有直接返回引用,否则将字符串拷贝到常量池中再返回其引用。一方面存在Perm空间有限,另一方面当常量池固定长度1009,当其中的字符串越来越多时,性能会急剧下降。

为了解决这个问题,jdk7及以后的实现中,将字符串常量池放在了Java堆中。当调用intern()方法时,有别于jdk6中的实现,不会再将该字符串拷贝到常量池中,而是仅在常量池记录下字符串首次出现的引用。这样做有利于复用对象,节省空间。
但是相对于jdk6的实现,时间效率稍微低一点。但是考虑到重复对象给gc带来的压力,这种时间上的损耗可以忽略不计。

所以不难发现,sec1中的代码在jdk6中Str1.intern()返回的实际上是str1在常量池中的拷贝。而jdk7,常量池仅仅记录了str1的引用。结果是不言而喻的。

sec2的代码为啥都返回false呢,这是因为在java中的关键字都是初始放在常量池中的,在sec2执行之前就已存在所以无论怎么调用intern(),结果都是false。

字符串字面量和常量池

考虑以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
// sec1
String s = new String("11");
s.intern();
String s2 = "11";
System.out.println(s == s2);
// sec2
String s3 = new String("2") + new String("2");
s3.intern();
String s4 = "22";
System.out.println(s3 == s4);

结果

1
2
3
// jdk7
fasle
true

==为何相似的代码会有不同的结果?==

问题的关键在于

String s = new String(“11”);

我们知道字符串字面量在解析的时候是放在常量池中的,
我们在new String(“11”)中的字面量也不例外,所以sec1
中的s.intern()实际返回的是自动放到常量池的“11”,也即s2指向的对象,s1是java堆中创建的另一对象,所以结果自然为false。

而sec2中的第一行相当于

String s3 = new StringBuilder(“2”).append(“2”).toString();

执行完后s3获得”22”,之后将其放入常量池中,str4中所指向的”22”和str3为同一对象。

  • 仓促之间,必有疏漏,还望指正
  • 2018.10.22 16:15