溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Java 虛擬機中的String 類和常量池

發(fā)布時間:2021-11-17 14:04:00 來源:億速云 閱讀:144 作者:柒染 欄目:軟件技術(shù)

本篇文章為大家展示了如何理解Java 虛擬機中的String 類和常量池,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。

一、String 對象的兩種創(chuàng)建方式

String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2); //false

這兩種不同的創(chuàng)建方法是有差別的:

第一種方式是在常量池中獲取對象(“abcd” 屬于字符串字面量,因此編譯時期會在常量池中創(chuàng)建一個字符串對象);

第二種方式一共會創(chuàng)建兩個字符串對象(前提是 String Pool 中還沒有 “abcd” 字符串對象)。

  • “abcd” 屬于字符串字面量,因此編譯時期會在常量池中創(chuàng)建一個字符串對象,該字符串對象指向這個 “abcd” 字符串字面量;

  • 使用 new 的方式會在堆中創(chuàng)建一個字符串對象。
    str1 指向常量池中的 “abcd”,而 str2 指向堆中的字符串對象。

二、intern() 方法

intern() 方法設(shè)計的初衷,就是重用 String 對象,以節(jié)省內(nèi)存消耗。

JDK6:當(dāng)調(diào)用intern方法的時候,如果字符串常量池先前已創(chuàng)建出該字符串對象,則返回常量池中的該字符串的引用。否則,將此字符串對象添加到字符串常量池中,并且返回該字符串對象的引用。

JDK6+:當(dāng)調(diào)用intern方法的時候,如果字符串常量池先前已創(chuàng)建出該字符串對象,則返回常量池中的該字符串的引用。否則,如果該字符串對象已存在與Java堆中,則將堆中對此對象的引用添加到字符串常量池中,并且返回該引用;如果堆中不存在,則在常量池中創(chuàng)建該字符串并返回其引用。

在 JVM 運行時數(shù)據(jù)區(qū)中的方法區(qū)有一個常量池,但是發(fā)現(xiàn)在 JDK 1.6 以后常量池被放置在了堆空間,因此常量池位置的不同影響到了 String 的 intern() 方法的表現(xiàn)。

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

JDK 1.6 及以下

上述代碼輸出結(jié)果:

false
false

解釋:
在 JDK 1.6 中所有的輸出結(jié)果都是 false,因為 JDK 1.6 以及以前版本中,常量池是放在 PermGen 區(qū)(屬于方法區(qū))中的,而方法區(qū)和堆區(qū)是完全分開的。

使用引號聲明的字符串會直接在字符串常量池中生成的,而 new 出來的 String 對象是放在堆空間中的。所以兩者的內(nèi)存地址肯定是不相同的,即使調(diào)用了 intern() 方法也是不影響的。

intern() 方法在 JDK 1.6 中的作用:比如 String s = new String(“1”);,再調(diào)用 s.intern(),此時返回值還是字符串”1”,表面上看起來好像這個方法沒什么用處。但實際上,在 JDK1.6 中:檢查字符串常量池里是否存在 “1” 這么一個字符串,如果存在,就返回池里的字符串;如果不存在,該方法會把 “1” 添加到字符串常量池中,然后再返回它的引用。

JDK 1.6 及以上

上述代碼輸出結(jié)果:

false
true

解釋:
String s= new String("1") 生成了字符串常量池中的 “1” 和堆空間中的字符串對象。

s.intern() s對象去字符串常量池中尋找后,發(fā)現(xiàn) “1” 已存在于常量池中。

String s2 = "1"生成 s2 的引用指向常量池中的 “1” 對象。

顯然,s 和 s2 的引用地址是不同的。

String s3 = new String("1") + new String("1")在字符串常量池中生成 “1”,并在堆空間中生成 s3 引用指向的對象(內(nèi)容為 “11”)。 注意此時常量池中是沒有 “11” 對象。

s3.intern()將 s3 中的 “11” 字符串放入字符串常量池中。 JDK 1.6 的做法是直接在常量池中生成一個 “11” 的對象。但在 JDK 1.7 中,常量池中不需要再存儲一份對象了,可以直接存儲堆中的引用。這份引用直接指向 s3 引用的對象,也就是說 s3.intern() == s3 會返回 true。

String s4 = "11", 這一行代碼會直接去常量池中創(chuàng)建,但是發(fā)現(xiàn)已經(jīng)有這個對象了,此時 s4 就是指向 s3 引用對象的一個引用。因此 s3 == s4 返回了true。

三、字符串拼接

String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的對象
String str4 = str1 + str2; //TODO:在堆上創(chuàng)建的新的對象      
String str5 = "string";//常量池中的對象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

注意:盡量避免多個字符串拼接,因為這樣會重新創(chuàng)建對象。 如果需要改變字符串的話,可以使用 StringBuilder 或者 StringBuffer。

面試題:String s1 = new String(“abc”);問創(chuàng)建了幾個對象?

創(chuàng)建2個字符串對象(前提是 String Pool 中還沒有 “abcd” 字符串對象)。

  • “abc” 屬于字符串字面量,因此編譯時期會在常量池中創(chuàng)建一個字符串對象,指向這個 “abcd” 字符串字面量;

  • 使用 new 的方式會在堆中創(chuàng)建一個字符串對象。
    (字符串常量”abc”在編譯期就已經(jīng)確定放入常量池,而 Java 堆上的”abc”是在運行期初始化階段才確定)。

String s1 = new String("abc");// 堆內(nèi)存的地址值
String s2 = "abc";
System.out.println(s1 == s2);// 輸出false
//因為一個是堆內(nèi)存,一個是常量池的內(nèi)存,故兩者是不同的。
System.out.println(s1.equals(s2));// 輸出true

上述內(nèi)容就是如何理解Java 虛擬機中的String 類和常量池,你們學(xué)到知識或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI