运行时常量池_从String的intern()到常量池
前言
在知乎上遇到一個(gè)剛學(xué)Java就接觸的字符串比較的問(wèn)題:?通常,根據(jù)"==比較的是地址,equals比較的是值"介個(gè)定理就能得到結(jié)果。但是String有些特殊,通過(guò)new String(string)生成的兩個(gè)同值的字符串地址就不相等,用其他方式來(lái)生成的兩個(gè)同值字符串地址就相等。
代碼如下:
// 第一種方式創(chuàng)建字符串,字面量賦值String str1 = "abc";
String str2 = "abc";
// 第二種方式創(chuàng)建字符串
String str3 = new String("xyz");
String str4 = new String("xyz");
System.out.println(str1 == str2); //true
System.out.println(str3 == str4); //false
同樣是創(chuàng)建字符串,兩對(duì)等值的字符串進(jìn)行比較為什么結(jié)果不一樣,這就涉及到了常量池和堆。眾所周知,第一種方式創(chuàng)建的字符串,是將"abc"這個(gè)字面量放到了常量池中,然后str1和str2都指向常量池中的"abc",所以兩個(gè)變量地址相同;第二種方式創(chuàng)建的字符串,是先在常量池中放入"xyz",然后通過(guò)構(gòu)造函數(shù)將常量池中的"xyz"拷貝一份到堆中生成新的String,和常量池中的"xyx"就沒(méi)有了關(guān)系,所以兩個(gè)變量指向的是堆中兩個(gè)不同的變量,所以兩個(gè)變量地址不同。?
那intern()又是啥?和常量池之間又有什么聯(lián)系?
常量池
常量池是存放字面量、符號(hào)引用或直接引用的地方。而常量池又分為class常量池和運(yùn)行時(shí)常量池。
class常量池
class常量池是存放編譯期類中的字面量和符號(hào)引用。上面的字符串"abc"就是字面量;符號(hào)引用就是類和接口的完全限定名,字段的名稱和描述符,方法的名稱和描述符。
如圖:?圖中的就是new String(String)這個(gè)方法在常量池中的名稱和描述符,即符號(hào)引用。
運(yùn)行時(shí)常量池
我們平時(shí)說(shuō)的常量池指的就是運(yùn)行時(shí)常量池。在類加載的解析階段,會(huì)將class常量池載入內(nèi)存中(JDK1.7之前位于方法區(qū),現(xiàn)在位于Heap中),并且將符號(hào)引用解析成直接引用,即根據(jù)對(duì)方法/類的描述信息指向內(nèi)存中對(duì)應(yīng)的方法/類。運(yùn)行時(shí)常量池具有動(dòng)態(tài)性,可以在運(yùn)行期添加新的變量進(jìn)入常量池。
intern()
先看一下intern()這個(gè)方法的描述:用二級(jí)英文水平翻譯一波,大意就是一個(gè)string調(diào)用intern()的時(shí)候,如果池中有和這個(gè)字符串值相等的字符串對(duì)象,就會(huì)將字符串池中的字符串對(duì)象返回;如果沒(méi)有,就將這個(gè)字符串添加進(jìn)去,并返回這個(gè)字符串的引用。字符串池由String類私有維護(hù)。
這里又引入了字符串池這個(gè)概念。
字符串池
字符串池存放的是常量池中字符串對(duì)象的引用,而不是字符串對(duì)象。通過(guò)第一種字面量賦值法創(chuàng)建的字符串會(huì)放在常量池中,字符串池就會(huì)存儲(chǔ)這個(gè)字符串對(duì)象的引用,當(dāng)再次在常量池創(chuàng)建字符串時(shí),會(huì)先從字符串池查看是否有此字符串的等值引用,如果有的話,直接指向此引用對(duì)應(yīng)的對(duì)象。而第二種方式創(chuàng)建的字符串,會(huì)在字符串池中查找是否有與構(gòu)造參數(shù)等值的字符串,以此決定是否需要在常量池新建字符串,然后拷貝常量池中字符串在Heap創(chuàng)建一個(gè)新的字符串。?如圖,在堆中會(huì)在常量池中創(chuàng)建一個(gè)名為original的新字符串,然后拷貝并在堆中生成一個(gè)新字符串。注釋中也提到,除非你需要一個(gè)字符串的顯式副本,否則不需要使用這個(gè)構(gòu)造函數(shù),因?yàn)樽址遣豢勺兊摹?/p>
這里使用intern()測(cè)試一下字符串池:
public static void main(String[] args) {//第一部分 測(cè)試
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1.intern() == str1); //true
System.out.println(str1.intern() == str2); //false
System.out.println(str1.intern() == str2.intern()); //true
//第二部分 測(cè)試通過(guò)char[]創(chuàng)建字符串后,引用是否會(huì)進(jìn)入字符串池
String str3 = new String(new char[]{'g', 'h'});
String str4 = "gh";
System.out.println(str3.intern() == str3); //false
System.out.println(str3.intern() == str4); //true
//第三部分 測(cè)試char[]創(chuàng)建的字符串調(diào)用intern()后引用是否進(jìn)入字符串池
String str3 = new String(new char[]{'g', 'h'});
str3.intern();
String str4 = "gh";
System.out.println(str3.intern() == str3); //true
System.out.println(str3.intern() == str4); //true
}
上面的三部分代碼是獨(dú)立測(cè)試。
第一部分:str1在常量池創(chuàng)建了abc,并將引用放入字符串池,str2拷貝常量池中的abc并在堆中創(chuàng)建新字符串。intern()從字符串池中獲取的是常量池中str1的abc引用。
第二部分:str3通過(guò)char[]在堆中創(chuàng)建了字符串,不是在常量池,所以gh的引用不會(huì)自動(dòng)放入字符串池。str4在常量池創(chuàng)建了gh,所以字符串池中保存了str4的gh引用。intern()從字符串池中獲取的是常量池中str4的gh引用。
第三部分:str3通過(guò)char[]在堆中創(chuàng)建了字符串,不是在常量池,所以gh的引用不會(huì)自動(dòng)放入字符串池,但是它調(diào)用intern()手動(dòng)將str3的gh的引用添加到了字符串池中。當(dāng)str4使用字面量賦值創(chuàng)建時(shí),查詢到字符串池中有g(shù)h的引用,str4就指向了str3的gh引用。intern()從字符串池中獲取的是堆中str3的gh引用。?
從上面的代碼中也得出結(jié)論:intern()可以將堆中創(chuàng)建的且字符串池沒(méi)有等值引用的字符串引用放入字符串池。
同時(shí),這也能說(shuō)明String為什么不可變這個(gè)問(wèn)題。因?yàn)檫@樣可以保證多個(gè)引用可以同時(shí)指向字符串池中的同一個(gè)對(duì)象。如果字符串是可變的,其中的一個(gè)引用操作改變了對(duì)象的值,對(duì)其他引用會(huì)有影響,這樣顯然是不可以的。
言歸正傳
回到知乎上的問(wèn)題。在常量池創(chuàng)建了"string"并將其引用放入字符串池,str1調(diào)用intern()返回的是常量池中的引用,而str1指向的是堆中的引用,所以輸出為false。
而StringBuilder的toString()是通過(guò)char[]創(chuàng)建字符串:?在堆中創(chuàng)建了abcdef之后,str2調(diào)用intern()將堆中引用放入字符串池并返回此引用,與str2指向堆中同一個(gè)字符串對(duì)象,所以輸出為true。
結(jié)語(yǔ)
Java中有時(shí)候很小的問(wèn)題也會(huì)發(fā)散出很多知識(shí)點(diǎn),不論是底層還是JVM的理論學(xué)習(xí),結(jié)合應(yīng)用案例會(huì)理解的更加深刻。就像文中提到的常量池就是class文件結(jié)構(gòu)和類加載理論學(xué)習(xí)的一部分。
總結(jié)
以上是生活随笔為你收集整理的运行时常量池_从String的intern()到常量池的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python 阿里云短信接口_阿里云短信
- 下一篇: vfp 右键发送邮件_邮件批量发送的方法