這篇文章主要講解了“Nacos中使用String.intern方法有什么用”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Nacos中使用String.intern方法有什么用”吧!
讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:域名與空間、網(wǎng)站空間、營銷軟件、網(wǎng)站建設(shè)、筠連網(wǎng)站維護、網(wǎng)站推廣。
面試的時候經(jīng)常被問到String的intern方法的調(diào)用及內(nèi)存結(jié)構(gòu)發(fā)生的變化。但在實際生產(chǎn)中真正用到過了嗎,看到過別人如何使用了嗎?
最近閱讀Nacos的源碼,還真看到代碼中使用String類的intern方法,NamingUtils類中有這樣一個方法:
public static String getGroupedName(final String serviceName, final String groupName) { // ...省略參數(shù)校驗部分 final String resultGroupedName = groupName + Constants.SERVICE_INFO_SPLITER + serviceName; return resultGroupedName.intern(); }
方法操作很簡單,就是拼接一個GrouedName的字符串,但為什么在最后調(diào)用了一下intern方法呢?本篇文章我們就來分析一下。
先來看一下String中intern方法的定義:
public native String intern();
發(fā)現(xiàn)是native的方法,暫時我們無法更進一步看到它的具體實現(xiàn)。很多朋友至此便淺嘗輒止了,其實我們還可以通過文檔說明及一些工具來驗證intern方法的作用及運作原理。
在intern方法上有一段注釋來介紹它的功能,大意是:當調(diào)用intern方法時,如果字符串常量池中不存在對應(yīng)的字符串(通過equals方法比較),則將該字符串添加到常量池中;如果存在則直接返回對應(yīng)地址。
我們都知道字符串常量池的功能類似緩存,它可以讓程序在運行的過程中速度更快、更節(jié)省內(nèi)存。而上述代碼之所以調(diào)用intern方法想必便是為了此目的。
要了解intern的作用,不得不先了解一下String字符串的內(nèi)存結(jié)構(gòu)。
字符串的創(chuàng)建通常有兩種形式,通過new關(guān)鍵字創(chuàng)建和通過引號直接賦值的形式。這兩種形式的字符串創(chuàng)建在內(nèi)存分布上是有區(qū)別的。
直接使用雙引號創(chuàng)建字符串時,會先去常量池查找該字符串是否已經(jīng)存在,如果不存在的話先在常量池創(chuàng)建常量對象,然后返回引用地址;如果存在,則直接返回。
JDK6及以前的內(nèi)存結(jié)構(gòu):
JDK7及以后的內(nèi)存結(jié)構(gòu):
PS:JDK8及以后Perm Space改為元空間了,這就不畫圖展示了。
而使用new關(guān)鍵字創(chuàng)建字符串時,創(chuàng)建的對象是分配在堆中的,棧中的引用指向該對象。
String str2 = new String("hello");
而雙引號中的字面值有兩種情況,當常量池中不存在字面值“hello”時,會在常量池中生成這樣一個常量;如果存在,則堆中的對象直接指向該字面值。
JDK6及以前的內(nèi)存結(jié)構(gòu):
JDK7及以后的內(nèi)存結(jié)構(gòu):
通常面試題中會問到通過new關(guān)鍵字創(chuàng)建String,內(nèi)存中創(chuàng)建了幾個對象,就是基于上面的原理來說的。很顯然,如果常量池中已經(jīng)存在“hello”了,那么只會在堆中創(chuàng)建一個對象,如果常量池中不存在,那就需要現(xiàn)在常量池中存儲字符串對象了。因此,答案可能是1個,也可能是2個。
了解了這兩個基礎(chǔ)的內(nèi)存邏輯與分布,基本延伸出來的情況(面試題)都可以應(yīng)答了。
比如:
String str1 = "hello"; String str2 = "hello"; System.out.println(str1 == str2);//true
兩個對象都是直接存放在常量池的,所以引用地址都一樣。
再比如:
String s1 = new String("hello"); String s2 = "hello"; String s3 = new String("hello"); System.out.println(s1 == s2);// false System.out.println(s1.equals(s2));// true System.out.println(s1 == s3);//false
其中第一個輸出為false是因為s1指向的是堆中的對象地址,s2指向的是常量池的地址;第二個比較的是常量池中存儲的字符串,它們共用一個,所以為true;第三個s1和s3雖然共用常量池中的“hello”字面值,但是它們分別在堆中有自己的對象,所以為false。
字符串的拼接
字符串的拼接分兩種情況,先看直接加號拼接:
String s1 = "hello" + "word"; String s2 = "helloword"; System.out,println(s1 == s2);//true
這種情況,針對s1,Java編譯器是會進行編譯期的優(yōu)化的,編譯器會進行字符串的拼接,然后存入常量池的為“helloword”。所以s1和s2都指向常量池中同樣的地址。
另外一種情況就是非純字符串常量的拼接:
String s1 = new String("he") + new String("llo");
針對這種情況,Java編譯器同樣會進行優(yōu)化,優(yōu)化為基于StringBuilder的字符串拼接。
基本流程,先創(chuàng)建一個StringBuilder,然后調(diào)用append的方法進行拼接,最后再調(diào)用toString方法生成字符串對象。最后通過toString方法生成的這個字符串“hello”,在常量池中是并不存在的。
最終的內(nèi)存結(jié)構(gòu)為:
而最開始講到的Nacos中的源碼,之所以拼接之后調(diào)用intern方法的目的就是將上面這種形式拼接的堆中的字符串存儲到常量池中。然后直接訪問常量池中的對象,從而提升性能。
那么,當String類調(diào)用intern之后發(fā)生了什么呢?我們下面來看一下。
String.intern()方法的功能前面我們已經(jīng)說過了,下面我們來看一下不同的JDK版本中使用intern方法的效果有何不同。
JDK1.6的實現(xiàn)
在JDK1.6及以前版本中,常量池在永久代分配內(nèi)存,永久代和Java堆的內(nèi)存是物理隔離的,執(zhí)行intern方法時,如果常量池不存在該字符串,虛擬機會在常量池中復制該字符串,并返回引用。
如果已經(jīng)存在該字符串了,則直接返回這個常量池中的這個常量對象的引用。所以需要謹慎使用intern方法,避免常量池中字符串過多,導致性能變慢,甚至發(fā)生PermGen內(nèi)存溢出。
String str1 = new String("abc"); String str1Pool = str1.intern(); System.out.println(str1Pool == str1);
上述代碼,在JDK1.6中打印結(jié)果為false。先看一下內(nèi)存結(jié)構(gòu)圖:
在上述代碼中,當new String時與前面分析的內(nèi)存結(jié)果一樣,會在常量池和堆中創(chuàng)建兩個對象。當str1調(diào)用intern方法時,發(fā)現(xiàn)常量池中已經(jīng)存在對應(yīng)的對象了,則該方法返回常量池中對象的地址。此時,str1指向堆中對象地址,str1Pool指向常量池中地址,因此不相等。
還有一種情況是常量池中本來不存在字符串常量:
String str1 = new String("a") + new String("bc"); String str1Pool = str1.intern(); System.out.println(str1Pool == str1);
對應(yīng)內(nèi)存結(jié)構(gòu)圖如下:
上述代碼中,字符串str1生成的對象在常量池中并不存在,完全存在于堆中。當然,字符串“a”和“bc”會在創(chuàng)建對象時存入常量池。而當調(diào)用intern方法之后,會檢查常量池中是否有“abc”,發(fā)現(xiàn)沒有,于是將“abc”復制到常量池中,intern返回的結(jié)果為常量池的地址。此時,很顯然,str1Pool和str1一個指向常量池,一個指向堆地址,因此不相等。
但在JDK1.7及以后,事情就發(fā)生了變化。
JDK1.7的實現(xiàn)
JDK1.7后,intern方法還是會先去查詢常量池中是否有已經(jīng)存在,如果存在,則返回常量池中的引用,與之前沒有區(qū)別。但如果在常量池找不到對應(yīng)的字符串,則不會再將字符串拷貝到常量池,而只是在常量池中生成一個對原字符串的引用。
簡單的說,就是往常量池放的內(nèi)容變了。原來在常量池中找不到時,復制一個副本放到常量池,1.7后則是將堆上的地址引用復制到常量池,也就是常量池存放的是堆中字符串的引用地址。
1.7及以后,常量池已經(jīng)從方法區(qū)中移出來到了堆中。
已經(jīng)存在的場景我們就不演示了,與JDK1.6一致。下面來看一下常量池不存在對應(yīng)字符串的情況。
String str1 = new String("a") + new String("bc"); String str1Pool = str1.intern(); System.out.println(str1Pool == str1);
對應(yīng)的內(nèi)存結(jié)構(gòu)變化如下:
最開始創(chuàng)建“abc”對象時與JDK1.6一樣,在堆中創(chuàng)建一個對象,常量池中并不存在“abc”。
當調(diào)用intern方法時,常量池不是復制“abc”字面值進行存儲,而是直接將堆中“abc”的地址存儲在常量池中,并且intern方法返回了堆中對象的地址。
此時會發(fā)現(xiàn)str1和str1Pool存儲的引用地址都是堆中“abc”的地址。因此上述方法執(zhí)行的結(jié)果為true。
Java使用jni調(diào)用c++實現(xiàn)的StringTable的intern方法,StringTable的intern方法跟Java中的HashMap的實現(xiàn)是差不多的,但不能自動擴容,默認大小是1009。
也就是說String的字符串常量池是一個固定大小的Hashtable。如果常量池的String非常多,就會造成Hash沖突嚴重,導致鏈表很長,直接后果是會造成當調(diào)用String.intern時性能大幅下降。
在JDK1.6中StringTable的長度是固定不變的1009。在JDK1.7中,StringTable的長度可以通過一個參數(shù)指定:
-XX:StringTableSize=99991
所以,在使用intern方法時需要慎重。那么,什么場景下適合使用intern方法呢?
就是對應(yīng)的字符串被大量重復使用的情況下。比如最開始我們講的Nacos代碼,它是服務(wù)的名稱基本上不會變化,而且會被重復的使用,放在常量池里面就比較合適了。
同時,我們要知道,雖然intern方法可以減少內(nèi)存占用率,但由于多了一步操作,會導致程序耗時增加。但這與JVM的垃圾回收耗時相比,增加的時間可以忽略不計。
感謝各位的閱讀,以上就是“Nacos中使用String.intern方法有什么用”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對Nacos中使用String.intern方法有什么用這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!
當前名稱:Nacos中使用String.intern方法有什么用
文章位置:http://chinadenli.net/article20/ihoejo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、關(guān)鍵詞優(yōu)化、微信公眾號、標簽優(yōu)化、網(wǎng)站排名、網(wǎng)頁設(shè)計公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)