中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

JVM字符串常量池及String的intern方法是什么樣的

發布時間:2021-10-23 16:22:09 來源:億速云 閱讀:130 作者:柒染 欄目:大數據

這篇文章給大家介紹JVM字符串常量池及String的intern方法是什么樣的,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

關于字符串的比較在前面文章中已經詳解過,本篇文章基于字符串常量池的存儲及在使用intern方法時所引起的內存變化進行一步深層次的講解。

重點內容:當字符串調用intern方法后,再進行字符串的比較,會發生什么變化?

面試題

先通過一個面試題形象的了解一下我們本篇文章要講的內容的呈現形式:

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);

執行上面的代碼,會發現打印的結果都是true。那么,為什么本來不相等的字符串,調用了intern方法之后便相等了呢?下面我們就來逐步分析這其中的底層實現。

intern方法的作用

intern()方法的功能定義:

(1)如果當前字符串內容存在于字符串常量池(即equals()方法為true,也就是內容一樣),那直接返回此字符串在常量池的引用;

(2)如果當前字符串不在字符串常量池中,那么在常量池創建一個引用并指向堆中已存在的字符串,然后返回常量池中的引用。

簡單說intern方法就是判斷并將字符串是否存在于字符串常量池,如果不存在則創建,存在則返回。

字符串常量池

在HotSpot中實現字符串常量池功能的是一個StringTable類,它是一個Hash表,默認值大小長度是1009。在每個HotSpot虛擬機的實例中只有一份,被所有的類共享。字符串常量由一個個字符組成,放在了StringTable上。

在《面試題系列第5篇:JDK的運行時常量池、字符串常量池、靜態常量池,還傻傻分不清?》一文中我們已經專門介紹了字符串常量池的位置隨著JDK版本的變化而變化,可以參考。

JDK6及之前版本,字符串常量池是放在Perm Gen區(方法區)中。StringTable的長度是固定的,長度是1009,當String字符串過多時會造成hash沖突,導致鏈表過長,性能大幅度下降。此時字符串常量池里面放的全部是字符串常量(字面值)。

由于永久代的空間有限且固定,JDK6的存儲模式很容易造成OutOfMemoryError。

而JDK7時正在著手去永久代的工作,因此字符串常量池被放在了堆中。此時,即使堆的大小也是固定的,但對于應用調優工作,只需要調整堆大小就行了。

在JDK7中字符串常量池不僅僅可以存放字符串常量,還可以存放字符串的引用。也就是說,堆中的字符串的引用可以作為常量池的值而存在。

字符串池化流程分析

在了解了上面的基礎理論,我們下面以圖文相結合的形式來逐步演示字符串池化的流程和分類。以下實例以JDK8版本為基礎來進行分析講解。

當我們通過雙引號聲明一個字符串:

String wechat = "程序新視界";

此時,雙引號內的字符串會被直接存儲在字符串常量池中。

JVM字符串常量池及String的intern方法是什么樣的

關于上面的存儲結構,我們已經在之前文章中提到,不再過多解釋。下面如果我們再聲明同樣的字符串看看會有什么樣的變化。

String wechat = "程序新視界";
String wechat1 = "程序新視界";

上述代碼中聲明wechat1時,會發現常量池中已經存在了對應的字符串,則不會再重新創建,只是把對應的引用返回給wechat1。對應結構圖如下: JVM字符串常量池及String的intern方法是什么樣的

此時,如果直接用雙等號比較wechat和wechat1肯定是相等的,因為它們的引用和字面值都是相同的。

上面是直接雙引號賦值的情況,那么如果通過new的形式創建字符串對應的流程又是如何呢?前面文章已經講到這分兩種情況:常量池存在對應的值和不存在對應的值。

String wechat2 = new String("程序新視界");

如果存在對應的值,此時會先在堆中創建一個針對wechat2變量的對象引用,然后將這個對象引用指向字符串常量池中已經存在的常量。 JVM字符串常量池及String的intern方法是什么樣的

此時直接使用雙等號比較wechat和wechat2變量肯定是不相等的,而通過equals方法進行對比字面值則是相等的。

另外一種情況就是通過new創建時,字符串常量池中并不存在對應的常量。這種情況會現在字符串常量池中創建一個字符串常量,然后再在堆中創建一個字符串,持有常量池中對應字符串的引用。并把堆中對象的地址返回給wechat2。最終效果圖依舊如上圖。

在此時,如果不是直接new字符串賦值,而是通過+號操作,情況就有所不同。

String s1 = "程序";
String wechat3 = new String(s1 + "新視界");

上述代碼s1會存入常量池,而wechat3的值則由于JVM編譯時采用了StringBuilder進行加號的拼接,只會在堆中創建一個String對象,并不會在常量池中存儲對應的字符串。

JVM字符串常量池及String的intern方法是什么樣的

此時的情況已經涉及到我們面試題中創建字符串的情況了。那么,下面我們就通過intern方法進行池化操作,看看字符串常量池的具體變化。

還以上面的代碼為例,此時wechat、wechat1、wechat2三個變量和wechat3直接用雙等號比較肯定是不相等的。下面對wechat3進行intern池化處理。

String s1 = "程序";
String wechat3 = new String(s1 + "新視界");
wechat3 = wechat3.intern();

此時會發現wechat、wechat1兩個變量與wechat3的值相等了。由于wechat和wechat1其實是一個,這里只以wechat和wechat3的比較為例來分析一下這個流程。

在沒有調用intern方法之前內存的狀態是下圖(忽略掉s1部分)這樣的:

JVM字符串常量池及String的intern方法是什么樣的

看上圖它們的值不相等也就不奇怪了。下面對wechat3進行池化處理,并把池化的結果賦值給wechat3,就是上面的代碼。內存結構會發生如下變化:

JVM字符串常量池及String的intern方法是什么樣的

此時,再判斷對應的兩個值,因為引用和字面值全部相同,因此便相等了。具體intern的判斷規則我們上面已經知道,如果常量池中存在對應的值,則直接返回引用。

那還有另外一種情況,就是常量池中不存在對應的值會是如何處理的呢?先看如下代碼:

String s2 = "關注";
String wechat4 = new String(s2 + "公眾號");
wechat4 = wechat4.intern();

在調用intern之前的操作我們前面已經說過,會在堆中創建一個String對象,而常量池中并不會存儲一份,與wechat3的圖一樣。

此時常量池中并未存在對應的字符串,此時調用intern方法之后,內存結構如下:

JVM字符串常量池及String的intern方法是什么樣的

經intern方法之后,常量池中存了堆中對應字符串的引用。對照上面說的,JDK7及之后字符串常量池中可以存儲引用了。

需要注意的是,當字符串常量池中并不存在對應字符串時,調用intern方法返回的地址為堆中的地址,對應圖中的0x99。而wechat4本來地址指向的就是堆中的地址,因此不會發生變化。

此時如果再定義一個雙引號賦值的wechat5,如下代碼:

String s2 = "關注";
String wechat4 = new String(s2 + "公眾號");
wechat4 = wechat4.intern();

String wechat5 = "關注公眾號";
System.out.println(wechat4 == wechat5);

變量wechat5初始化時發現字符串常量池中已經存在了一個引用,那么wechat5會直接指向這個引用,也就是wechat5和wechat4一樣,都指向內存中的String對象。 JVM字符串常量池及String的intern方法是什么樣的

小結

上面這個演示實例時需要注意的重點是intern方法返回的引用地址。如果字符串常量池中已經存在對應的字符串時,此時返回的是字符串常量的地址【常量池中存儲的是字符串】,如果字符串常量池中不存在對應的字符串,此時會把堆中的引用放在常量池對應的位置【常量池中存儲的是堆中字符串的引用】,此時intern返回的是堆中字符串對應的引用。

搞清楚了上面的返回邏輯再看最初的代碼:

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為堆中另外一個“hello”字符串的地址。當s1.intern(),常量池中存儲了s1的地址,此時s1.intern()返回的也是s1的地址,因此s1=s3,都是同一個地址嘛。

然后執行s2.intern(),此時常量池中已經有hello字符串,類型為引用且指向s1的地址,執行之后返回的便是s1的地址,賦值給s4,因此s1和s4也指向同一個地址,因此相等。

通過上面的更深層次的分析,想必大家對字符串常量、字符串常量池以及intern方法有了更加深刻的理解。

關于JVM字符串常量池及String的intern方法是什么樣的就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

桦川县| 凤山县| 法库县| 祁门县| 寻乌县| 万全县| 当雄县| 宁远县| 杭锦后旗| 佳木斯市| 金坛市| 互助| 龙口市| 吴桥县| 南安市| 塘沽区| 社旗县| 达日县| 石城县| 博爱县| 泸溪县| 眉山市| 中山市| 珠海市| 尉犁县| 克拉玛依市| 徐汇区| 普宁市| 伊川县| 马尔康县| 丰镇市| 余姚市| 榆树市| 临潭县| 中宁县| 宁陵县| 绥阳县| 靖西县| 碌曲县| 历史| 平乐县|