您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“JVM字符串常量池及String的intern方法實(shí)例分析”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“JVM字符串常量池及String的intern方法實(shí)例分析”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
先通過一個(gè)面試題形象的了解一下我們本篇文章要講的內(nèi)容的呈現(xiàn)形式:
String s1 = new String("he") + new String("llo"); String s2 = new String("h") + new String("ello"); String s3 = s1.intern(); String s4 = s2.intern(); System.out.println(s1 == s3); System.out.println(s1 == s4);
執(zhí)行上面的代碼,會(huì)發(fā)現(xiàn)打印的結(jié)果都是 true 。那么,為什么本來不相等的字符串,調(diào)用了intern
方法之后便相等了呢?下面我們就來逐步分析這其中的底層實(shí)現(xiàn)。
intern()
方法的功能定義:
(1)如果當(dāng)前字符串內(nèi)容存在于字符串常量池(即equals()方法為true,也就是內(nèi)容一樣),那直接返回此字符串在常量池的引用;
(2)如果當(dāng)前字符串不在字符串常量池中,那么在常量池創(chuàng)建一個(gè)引用并指向堆中已存在的字符串,然后返回常量池中的引用。
簡(jiǎn)單說intern
方法就是判斷并將字符串是否存在于字符串常量池,如果不存在則創(chuàng)建,存在則返回。
在HotSpot
中實(shí)現(xiàn)字符串常量池功能的是一個(gè)StringTable
類,它是一個(gè)Hash
表,默認(rèn)值大小長(zhǎng)度是1009。在每個(gè)HotSpot
虛擬機(jī)的實(shí)例中只有一份,被所有的類共享。字符串常量由一個(gè)個(gè)字符組成,放在了StringTable
上。
JDK6及之前版本,字符串常量池是放在Perm Gen
區(qū)(方法區(qū))中。StringTable
的長(zhǎng)度是固定的,長(zhǎng)度是1009,當(dāng)String
字符串過多時(shí)會(huì)造成hash
沖突,導(dǎo)致鏈表過長(zhǎng),性能大幅度下降。此時(shí)字符串常量池里面放的全部是字符串常量(字面值)。
由于永久代的空間有限且固定,JDK6
的存儲(chǔ)模式很容易造成OutOfMemoryError
。
而JDK7
時(shí)正在著手去永久代的工作,因此字符串常量池被放在了堆中。此時(shí),即使堆的大小也是固定的,但對(duì)于應(yīng)用調(diào)優(yōu)工作,只需要調(diào)整堆大小就行了。
在JDK7
中字符串常量池不僅僅可以存放字符串常量,還可以存放字符串的引用。也就是說,堆中的字符串的引用可以作為常量池的值而存在。
在了解了上面的基礎(chǔ)理論,我們下面以圖文相結(jié)合的形式來逐步演示字符串池化的流程和分類。以下實(shí)例以JDK8
版本為基礎(chǔ)來進(jìn)行分析講解。
當(dāng)我們通過雙引號(hào)聲明一個(gè)字符串:
String wechat = "程序新視界";
此時(shí),雙引號(hào)內(nèi)的字符串會(huì)被直接存儲(chǔ)在字符串常量池中。
關(guān)于上面的存儲(chǔ)結(jié)構(gòu),我們已經(jīng)在之前文章中提到,不再過多解釋。下面如果我們?cè)俾暶魍瑯拥淖址纯磿?huì)有什么樣的變化。
String wechat = "程序新視界"; String wechat1 = "程序新視界";
上述代碼中聲明wechat1
時(shí),會(huì)發(fā)現(xiàn)常量池中已經(jīng)存在了對(duì)應(yīng)的字符串,則不會(huì)再重新創(chuàng)建,只是把對(duì)應(yīng)的引用返回給wechat1
。
此時(shí),如果直接用雙等號(hào)比較wechat
和wechat1
肯定是相等的,因?yàn)樗鼈兊囊煤妥置嬷刀际窍嗤摹?/p>
上面是直接雙引號(hào)賦值的情況,那么如果通過 new 的形式創(chuàng)建字符串對(duì)應(yīng)的流程又是如何呢?前面文章已經(jīng)講到這分兩種情況:常量池存在對(duì)應(yīng)的值和不存在對(duì)應(yīng)的值。
String wechat2 = new String("程序新視界");
如果存在對(duì)應(yīng)的值,此時(shí)會(huì)先在堆中創(chuàng)建一個(gè)針對(duì)wechat2
變量的對(duì)象引用,然后將這個(gè)對(duì)象引用指向字符串常量池中已經(jīng)存在的常量。
此時(shí)直接使用雙等號(hào)比較wechat
和wechat2
變量肯定是不相等的,而通過equals
方法進(jìn)行對(duì)比字面值則是相等的。
另外一種情況就是通過 new 創(chuàng)建時(shí),字符串常量池中并不存在對(duì)應(yīng)的常量。這種情況會(huì)現(xiàn)在字符串常量池中創(chuàng)建一個(gè)字符串常量,然后再在堆中創(chuàng)建一個(gè)字符串,持有常量池中對(duì)應(yīng)字符串的引用。并把堆中對(duì)象的地址返回給wechat2
。最終效果圖依舊如上圖。
在此時(shí),如果不是直接new字符串賦值,而是通過+號(hào)操作,情況就有所不同。
String s1 = "程序"; String wechat3 = new String(s1 + "新視界");
上述代碼 s1 會(huì)存入常量池,而wechat3
的值則由于JVM
編譯時(shí)采用了StringBuilder
進(jìn)行加號(hào)的拼接,只會(huì)在堆中創(chuàng)建一個(gè)String
對(duì)象,并不會(huì)在常量池中存儲(chǔ)對(duì)應(yīng)的字符串。
此時(shí)的情況已經(jīng)涉及到我們面試題中創(chuàng)建字符串的情況了。那么,下面我們就通過intern
方法進(jìn)行池化操作,看看字符串常量池的具體變化。
還以上面的代碼為例,此時(shí)wechat
、wechat1
、wechat2
三個(gè)變量和wechat3
直接用雙等號(hào)比較肯定是不相等的。下面對(duì)wechat3
進(jìn)行intern
池化處理。
String s1 = "程序"; String wechat3 = new String(s1 + "新視界"); wechat3 = wechat3.intern();
此時(shí)會(huì)發(fā)現(xiàn)wechat
、wechat1
兩個(gè)變量與wechat3
的值相等了。由于wechat
和wechat1
其實(shí)是一個(gè),這里只以wechat
和wechat3
的比較為例來分析一下這個(gè)流程。
在沒有調(diào)用intern
方法之前內(nèi)存的狀態(tài)是下圖(忽略掉s1部分)這樣的
看上圖它們的值不相等也就不奇怪了。下面對(duì)wechat3
進(jìn)行池化處理,并把池化的結(jié)果賦值給wechat3
,就是上面的代碼。內(nèi)存結(jié)構(gòu)會(huì)發(fā)生如下變化
此時(shí),再判斷對(duì)應(yīng)的兩個(gè)值,因?yàn)橐煤妥置嬷等肯嗤?,因此便相等了。具體intern
的判斷規(guī)則我們上面已經(jīng)知道,如果常量池中存在對(duì)應(yīng)的值,則直接返回引用。
那還有另外一種情況,就是常量池中不存在對(duì)應(yīng)的值會(huì)是如何處理的呢?先看如下代碼:
String s2 = "關(guān)注"; String wechat4 = new String(s2 + "公眾號(hào)"); wechat4 = wechat4.intern();
在調(diào)用intern之前的操作我們前面已經(jīng)說過,會(huì)在堆中創(chuàng)建一個(gè)String對(duì)象,而常量池中并不會(huì)存儲(chǔ)一份,與wechat3的圖一樣。
此時(shí)常量池中并未存在對(duì)應(yīng)的字符串,此時(shí)調(diào)用intern方法之后
經(jīng)intern
方法之后,常量池中存了堆中對(duì)應(yīng)字符串的引用。對(duì)照上面說的,JDK7
及之后字符串常量池中可以存儲(chǔ)引用了。
需要注意的是,當(dāng)字符串常量池中并不存在對(duì)應(yīng)字符串時(shí),調(diào)用intern
方法返回的地址為堆中的地址,對(duì)應(yīng)圖中的0x99。而wechat4
本來地址指向的就是堆中的地址,因此不會(huì)發(fā)生變化。
此時(shí)如果再定義一個(gè)雙引號(hào)賦值的wechat5
,如下代碼:
String s2 = "關(guān)注"; String wechat4 = new String(s2 + "公眾號(hào)"); wechat4 = wechat4.intern(); String wechat5 = "關(guān)注公眾號(hào)"; System.out.println(wechat4 == wechat5);
變量wechat5
初始化時(shí)發(fā)現(xiàn)字符串常量池中已經(jīng)存在了一個(gè)引用,那么wechat5
會(huì)直接指向這個(gè)引用,也就是wechat5
和wechat4
一樣,都指向內(nèi)存中的String
對(duì)象。
上面這個(gè)演示實(shí)例時(shí)需要注意的重點(diǎn)是intern
方法返回的引用地址。如果字符串常量池中已經(jīng)存在對(duì)應(yīng)的字符串時(shí),此時(shí)返回的是字符串常量的地址【常量池中存儲(chǔ)的是字符串】,如果字符串常量池中不存在對(duì)應(yīng)的字符串,此時(shí)會(huì)把堆中的引用放在常量池對(duì)應(yīng)的位置【常量池中存儲(chǔ)的是堆中字符串的引用】,此時(shí)intern
返回的是堆中字符串對(duì)應(yīng)的引用。
搞清楚了上面的返回邏輯再看最初的代碼:
String s1 = new String("he") + new String("llo"); String s2 = new String("h") + new String("ello"); String s3 = s1.intern(); String s4 = s2.intern(); System.out.println(s1 == s3); System.out.println(s1 == s4);
其中 s1 為堆中字符串“hello”的地址;s2 為堆中另外一個(gè)“hello”字符串的地址。當(dāng)s1.intern()
,常量池中存儲(chǔ)了 s1 的地址,此時(shí)s1.intern()
返回的也是 s1 的地址,因此s1=s3,都是同一個(gè)地址嘛。
然后執(zhí)行s2.intern()
,此時(shí)常量池中已經(jīng)有 hello 字符串,類型為引用且指向 s1 的地址,執(zhí)行之后返回的便是 s1 的地址,賦值給 s4 ,因此 s1 和 s4 也指向同一個(gè)地址,因此相等。
通過上面的更深層次的分析,想必大家對(duì)字符串常量、字符串常量池以及intern
方法有了更加深刻的理解。相關(guān)的面試題如果按照這個(gè)思路分析,基本上都可以進(jìn)行準(zhǔn)確解答了。
讀到這里,這篇“JVM字符串常量池及String的intern方法實(shí)例分析”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。