最近在讀 《深入理解java虛擬機(jī)》泞坦,總結(jié)一下 String.intern 知識(shí)點(diǎn),引入書中的一個(gè)題目:
public class RuntimeConstantPoolOOM {
public static void main(String[] args){
String str1 = new StringBuilder("計(jì)算機(jī)").append("軟件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}
這段代碼在JDK 1.6中運(yùn)行砖顷,會(huì)得到兩個(gè)false贰锁,而在JDK1.7中運(yùn)行,會(huì)得到一個(gè)true和一個(gè)false滤蝠。產(chǎn)生差異的原因是:在JDK1.6中豌熄,intern()方法會(huì)把首次遇到的字符串實(shí)例復(fù)制到永久代中,返回的也是永久代中這個(gè)字符串實(shí)例的引用物咳,而StringBuildler創(chuàng)建的字符串實(shí)例在Java堆上锣险,所以必然不是一個(gè)引用,將返回false览闰。而JDK1.7中(以及部分其他虛擬機(jī)芯肤,例如JRockit)的intern()實(shí)現(xiàn)不會(huì)再 復(fù)制實(shí)例,只是在常量池中記錄首次出現(xiàn)實(shí)例的引用焕济,因此intern()返回的引用和由StringBuilder創(chuàng)建的字符串實(shí)例是同一個(gè);對(duì)str2比較返回false是因?yàn)椤癹ava”這個(gè)字符串在執(zhí)行StringBuilder.toString()之前已經(jīng)出現(xiàn)過纷妆,字符串常量池中已經(jīng)有它的引用盔几,不符合首次出現(xiàn)的規(guī)則晴弃,而"計(jì)算機(jī)軟件"這個(gè)字符串則是首次出現(xiàn)的,因?yàn)榉祷豻rue;
下面引入幾幅圖對(duì) intern 方法作如下總結(jié):
- new String 都是在堆上創(chuàng)建字符串對(duì)象逊拍。當(dāng)調(diào)用 intern() 方法時(shí)上鞠,JDK1.6中編譯器會(huì)將字符串添加到常量池中,并返回指向該常量的引用。
- 通過字面量賦值創(chuàng)建字符串(如:String str=”twm”)時(shí)芯丧,會(huì)先在常量池中查找是否存在相同的字符串芍阎,若存在,則將棧中的引用直接指向該字符串缨恒;若不存在谴咸,則在常量池中生成一個(gè)字符串球恤,再將棧中的引用指向該字符串具温。
- 常量字符串的“+”操作,編譯階段直接會(huì)合成為一個(gè)字符串世蔗。如string str=”JA”+”VA”萧锉,在編譯階段會(huì)直接合并成語句String str=”JAVA”珊随,于是會(huì)去常量池中查找是否存在”JAVA”,從而進(jìn)行創(chuàng)建或引用。
- 對(duì)于final字段,編譯期直接進(jìn)行了常量替換(而對(duì)于非final字段則是在運(yùn)行期進(jìn)行賦值處理的)叶洞。
final String str1 = ”ja” ;
final String str2 = ”va” ;
String str3 = str1+str2 ;
在編譯時(shí)鲫凶,直接替換成了 String str3 = ”ja” + ”va”,根據(jù)第三條規(guī)則衩辟,再次替換成 String str3=”JAVA” 螟炫。
- 常量字符串和變量拼接時(shí)(如:String str3 = baseStr + “01” ;)會(huì)調(diào)用 stringBuilder.append() 在堆上創(chuàng)建新的對(duì)象。
- JDK 1.7 后惭婿,intern 方法還是會(huì)先去查詢常量池中是否有已經(jīng)存在不恭,如果存在,則返回常量池中的引用财饥,這一點(diǎn)與之前沒有區(qū)別换吧,區(qū)別在于,如果在常量池找不到對(duì)應(yīng)的字符串钥星,則不會(huì)再將字符串拷貝到常量池沾瓦,而只是在常量池中生成一個(gè)對(duì)原字符串的引用。簡單的說谦炒,就是往常量池放的東西變了:原來在常量池中找不到時(shí)贯莺,復(fù)制一個(gè)副本放到常量池,1.7后則是將在堆上的地址引用復(fù)制到常量池宁改。
另外參考 關(guān)于String.intern()和new StringBuilder("").append("").toString();
先運(yùn)行這個(gè)代碼 ①
String str3 = new StringBuilder("ni").append("hao").toString();
System.out.println(str3==str3.intern());
通過上面的解釋缕探,運(yùn)行結(jié)果為true.
在運(yùn)行這個(gè)代碼 ②
String str3 = new StringBuilder("nihao").toString();
System.out.println(str3==str3.intern());
其結(jié)果是什么 ? 應(yīng)該還是 true 吧 ,畢竟通過上一個(gè)運(yùn)行結(jié)果可以知道 "nihao" 這個(gè)字符串常量沒有被預(yù)先加載到常量池中 还蹲。
但是運(yùn)行結(jié)果卻是 false .
解釋如下 :上面的 ① 代碼等價(jià)于下面的代碼
String a = "ni";
String b = "hao";
String str3 = new StringBuilder(a).append(b).toString();
System.out.println(str3==str3.intern());
很容易分析出 :
“nihao” 最先創(chuàng)建在堆中 str3.intern() 然后緩存在字符串常連池中 運(yùn)行結(jié)果為 true .
代碼 ② 等價(jià)于
String a = "nihao";
String str3 = new StringBuilder(a).toString();
System.out.println(str3==str3.intern());
很容易分析出:
“nihao” 最先創(chuàng)建在常量池中, 運(yùn)行結(jié)果為false.
new String()和new StringBuilder()同樣的原理爹耗,由此對(duì)于一道 經(jīng)典的Java面試題
在Java中,new String("hello")這樣的創(chuàng)建方式谜喊,到底創(chuàng)建了幾個(gè)String對(duì)象 ?
題目下答案眾說紛紜潭兽,有說1的有說2的,我覺得各有各的道理斗遏,如果常量池中有“hello”的字符串山卦,當(dāng)然只會(huì)創(chuàng)建1個(gè),如果沒有則會(huì)創(chuàng)建2個(gè)诵次;
new String ("hello")相當(dāng)于如下代碼:
String temp = "hello"; // 在常量池中
String str = new String(temp); // 在堆上