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