關於String的賦值創建和new String的過程的解讀

關於String的創建過程和intern()解釋

1.字面值和new創建

String:不可改變的Unicode字符序列,它的創建是一種池化思想,把需要共享的數據放在池中,用一個存儲區域來存放一些公用資源以減少存儲空間的開銷。String類的數據都會存放在字符串常量池中,JDK1.7後該常量池被移到了堆中!

		String s1 = "HelloWorld";
		String s2=new String("HelloWorld");
		String s3 = "Hello";
		String s4 = "World";
		String s5 = "Hello" + "World";
		String s6 = s3 + s4;	
		System.out.println(s1 == s5);
		System.out.println(s1 == s2);
		System.out.println(s1 == s6);
		System.out.println(s2 == s6);

結果:
	true
	false
	false
	false

​ 首先解釋一下字面值創建的過程:當運行到String s1 ="helloworld"時。s1是存放在棧中的,先查看字符串常量池中是否有"helloworld"這個字符串,如果有就將這個字符串的地址賦給s1,如果沒有就在常量池中創建然後在賦給s1。

對於s2:首先在堆中創建一個helloworld字符串對象,同時會在常量池中創建字符串對象,但是發現已經有了,就不會再去創建(到這個創建過程結束,不會再有其他操作)。此時的s2指向的是堆空間中的字符串對象。所以s1!=s2。

對於s5:這個會創建3個字符串對象,hello,world,和helloworld,但是常量池中已經有helloworld的所以並不會創建,s5指向的就是字符串常量池中的對象。

對於s6:這裏其實就相當於 s6 = new String(s3 + s4); s6 是存放於堆中的,不是字面量。所以 s1 不等於 s6 。

很顯然s2肯定是不等於s6的,每次new都會創建一個新對象

2.intern()

先看API文檔的解釋:

public String intern()
    返回字符串對象的規範化表示形式。一個初始爲空的字符串池,它由類 String 私有地維護。

當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法確定),則返回池中的字符串。否則,將此 String 對象添加到池中,並返回此 String 對象的引用。

它遵循以下規則:對於任意兩個字符串 s 和 t,當且僅當 s.equals(t)true 時,s.intern() == t.intern() 才爲 true

​ 文檔中說的很詳細:當調用 intern() 方法時,編譯器會將字符串添加到常量池中(stringTable維護),並返回指向該常量的引用。

​ JDK 1.7後,intern方法還是會先去查詢常量池中是否有已經存在,如果存在,則返回常量池中的引用,這一點與之前沒有區別,區別在於,如果在常量池找不到對應的字符串,則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。簡單的說,就是往常量池放的東西變了:原來在常量池中找不到時,複製一個副本放到常量池,1.7後則是將在堆上的地址引用複製到常量池。

​ 直接通過幾個例子來說明:

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
 
String s3 = new String("1") + new String("2");
s3.intern();
String s4 = "12";
System.out.println(s3 == s4);

結果:
	false
	true	

解釋:對於**第一段不僅在堆中創建了字符串對象,而且也在字符串常量池中也創建了字符串對象。**接着s調用了intern(),此時背後隱含的操作是:先去字符串常量池中尋找是否有equals相等的String對象,發現已經存在,就返回這個對象的引用地址,但是這裏並沒有變量來接受 ,所以此操作沒有帶來任何影響。在棧中的引用s指向的是堆中的字符串對象,棧中的s2指向的是字符串常量池中的對象,故不相等,如果是sysout(s.intern==s2)結果爲true。

​ 對於這裏有些博主認爲new String(“1”),只會在堆上創建一個字符串對象,但是這樣寫無法解釋上面的例子爲false。s.intern() 發現常量池中沒有字符串對象,那麼就會將堆上的引用複製到常量池中,這樣s2引用指向的就是堆中對象的地址,結果應爲true。故new String()在堆和常量池中均會創建對象。

對於第二段:new String(“1”) + new String(“1”)注意:這裏會在堆中創建3個字符串對象"1"對象和"2"對象還有"12"對象,在字符串常量池中只有2個對象"1"對象和"2"對象,因爲我沒有明確的new String(“12”),所以字符串常量池中並不會存在"12"這個對象。

​ 那麼接下來就好解釋了,調用intern()時,發現字符串常量池中並不存在"12"對象而堆中有,就將堆中對象的引用存到字符串常量池中(注意這個是JDK1.7開始改變的),s4存的實際就是堆內存中的字符串對象地址,因此s3==s4爲true。

3.有關運行時常量池和字符串常量池JDK1.7之前和之後的變化

在JDK1.7之前運行時常量池邏輯包含字符串常量池存放在方法區, 此時hotspot虛擬機對方法區的實現爲永久代

在JDK1.7 字符串常量池被從方法區拿到了堆中, 這裏沒有提到運行時常量池,也就是說字符串常量池被單獨拿到堆,運行時常量池剩下的東西還在方法區, 也就是hotspot中的永久代

在JDK1.8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時候字符串常量池還在堆, 運行時常量池還在方法區, 只不過方法區的實現從永久代變成了元空間(Metaspace)

參考:intern():https://blog.csdn.net/soonfly/article/details/70147205

​ 字符串常量池:https://blog.csdn.net/q5706503/article/details/84640762

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章