關(guān)于intern()

? ? 開始介紹 intern()方法前,先看一個(gè)簡單的 Java程序吧!

publicclassIntern{

? ? // 測試 String.intern()的使用

? ? ?publicstaticvoid main(String[] args) {? ? ? ??

? ? ? ? ? ? ? ? ?String str1 ="abc";? ? ??

? ? ? ? ? ? ? ? ?String str2 ="abc";? ? ? ?

? ? ? ? ? ? ? ? ? String str3 ="a";? ? ??

????????????????? String str4 ="bc";? ? ? ?

?????????????????String str5 = str3 + str4;? ? ? ??

? ? ? ? ? ? ? ? ?String str6 =newString(str1);


?????????????????print("------no intern------");? ? ? ?

? ? ? ? ? ? ? ? ? printnb("str1 == str2 ? ");print( str1 == str2);? ? ? ??

? ? ? ? ? ? ? ? ? ?printnb("str1 == str5 ? ");print(str1 == str5);? ? ?

?????????????????? printnb("str1 == str6 ? ");print(str1 == str6);

????????????????????print();


? ? ? ? ? ? ? ? ? ?print("------intern------");? ? ?

?????????????????? printnb("str1.intern() == str2.intern() ? "); print(str1.intern() == str2.intern());? ? ? ?

? ? ? ? ? ? ? ? ? ?printnb("str1.intern() == str5.intern() ? ");print(str1.intern() == str5.intern());? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?printnb("str1.intern() == str6.intern() ? ");print(str1.intern() == str6.intern());? ? ? ?

????????????????????printnb("str1 == str6.intern() ? ");print(str1 == str6.intern());? ? }}


the true answer is over here:

------no intern------

str1 == str2 ?true

str1 == str5 ?false

str1 == str6 ?false

-----intern------

str1.intern() == str2.intern() ?true

str1.intern() == str5.intern() ?true

str1.intern() == str6.intern() ?true

str1 == str6.intern() ?true

** 初步解析 **

------no intern------

Java語言會(huì)使用常量池保存那些在編譯器就已確定的已編譯的class文件中的一份數(shù)據(jù),主要有類留夜、接口撬碟、方法中的常量,以及一些以文本形式出現(xiàn)的符號引用底靠,如:類和接口的全限定名害晦;字段的名稱和描述符;方法和名稱和描述符等暑中。因此在編譯完Intern類后壹瘟,生成的class文件中會(huì)在常量池中保存“abc”、“a”和“bc”三個(gè)String常量鳄逾。

變量str1和str2均保存的是常量池中“abc”的引用稻轨,所以str1==str2成立;

在執(zhí)行 str5 = str3 + str4這句時(shí)雕凹,JVM會(huì)先創(chuàng)建一個(gè)StringBuilder對象殴俱,通過StringBuilder.append()方法將str3與str4的值拼接政冻,然后通過StringBuilder.toString()返回一個(gè)String對象,賦值給str5线欲,因此str1和str5指向的不是同一個(gè)String對象明场,str1 == str5不成立;

String str6 = new String(str1)一句顯式創(chuàng)建了一個(gè)新的String對象李丰,因此str1 == str6不成立便是顯而易見的事了苦锨。

------intern------

上面沒有使用intern()方法的字符串比較相對比較好理解,然而下面這部分使用了intern()方法的字符串比較操作才是本文的重點(diǎn)嫌套∧媛牛看到答案的你有沒有一臉懵逼?

String.intern()使用原理

查看 Java String類源碼踱讨,可以看到 intern()方法的定義如下:

publicnativeStringintern();

String.intern()是一個(gè)Native方法魏蔗,底層調(diào)用C++的 StringTable::intern方法實(shí)現(xiàn)。

當(dāng)通過語句str.intern()調(diào)用intern()方法后痹筛,JVM 就會(huì)在當(dāng)前類的常量池中查找是否存在與str等值的String莺治,若存在則直接返回常量池中相應(yīng)Strnig的引用;若不存在帚稠,則會(huì)在常量池中創(chuàng)建一個(gè)等值的String谣旁,然后返回這個(gè)String在常量池中的引用。因此滋早,只要是等值的String對象榄审,使用intern()方法返回的都是常量池中同一個(gè)String引用,所以杆麸,這些等值的String對象通過intern()后使用==是可以匹配的搁进。

由此就可以理解上面代碼中------intern------部分的結(jié)果了。因?yàn)閟tr1昔头、str5和str6是三個(gè)等值的String饼问,所以通過intern()方法,他們均會(huì)指向常量池中的同一個(gè)String引用揭斧,因此str1.intern() == str5.intern() == str6.intern()均為true莱革。

String.intern() in Java 6

Java 6中常量池位于PermGen(永久代)中,PermGen是一塊主要用于存放已加載的類信息和字符串池的大小固定的區(qū)域讹开。執(zhí)行intern()方法時(shí)盅视,若常量池中不存在等值的字符串,JVM就會(huì)在常量池中*** 創(chuàng)建一個(gè)等值的字符串***萧吠,然后返回該字符串的引用左冬。除此以外,JVM 會(huì)自動(dòng)在常量池中保存一份之前已使用過的字符串集合纸型。

** Java 6中使用intern()方法的主要問題就在于常量池被保存在PermGen中 **

首先,PermGen是一塊大小固定的區(qū)域,一般狰腌,不同的平臺PermGen的默認(rèn)大小也不相同除破,大致在32M到96M之間。所以不能對不受控制的運(yùn)行時(shí)字符串(如用戶輸入信息等)使用intern()方法琼腔,否則很有可能會(huì)引發(fā)PermGen內(nèi)存溢出瑰枫;

其次,String對象保存在 Java堆區(qū)丹莲,Java堆區(qū)與PermGen是物理隔離的光坝,因此,如果對多個(gè)不等值的字符串對象執(zhí)行intern操作甥材,則會(huì)導(dǎo)致內(nèi)存中存在許多重復(fù)的字符串盯另,會(huì)造成性能損失。

String.intern() in Java 7

Java 7將常量池從PermGen區(qū)移到了Java堆區(qū)洲赵,執(zhí)行intern操作時(shí)鸳惯,如果常量池已經(jīng)存在該字符串,則直接返回字符串引用叠萍,否則*** 復(fù)制該字符串對象的引用*** 到常量池中并返回芝发。

堆區(qū)的大小一般不受限,所以將常量池從PremGen區(qū)移到堆區(qū)使得常量池的使用不再受限于固定大小苛谷。除此之外辅鲸,位于堆區(qū)的常量池中的對象可以被垃圾回收。當(dāng)常量池中的字符串不再存在指向它的引用時(shí)腹殿,JVM就會(huì)回收該字符串独悴。

可以使用 -XX:StringTableSize 虛擬機(jī)參數(shù)設(shè)置字符串池的map大小。字符串池內(nèi)部實(shí)現(xiàn)為一個(gè)HashMap赫蛇,所以當(dāng)能夠確定程序中需要intern的字符串?dāng)?shù)目時(shí)绵患,可以將該map的size設(shè)置為所需數(shù)目*2(減少hash沖突),這樣就可以使得String.intern()每次都只需要常量時(shí)間和相當(dāng)小的內(nèi)存就能夠?qū)⒁粋€(gè)String存入字符串池中悟耘。

-XX:StringTableSize的默認(rèn)值:Java 7u40以前為:1009落蝙,Java 7u40以后:60013

intern()適用場景

Java 6中常量池位于PermGen區(qū),大小受限暂幼,所以不建議適用intern()方法筏勒,當(dāng)需要字符串池時(shí),需要自己使用HashMap實(shí)現(xiàn)旺嬉。

Java7管行、8中,常量池由PermGen區(qū)移到了堆區(qū)邪媳,還可以通過-XX:StringTableSize參數(shù)設(shè)置StringTable的大小捐顷,常量池的使用不再受限荡陷,由此可以重新考慮使用intern()方法。

intern()方法優(yōu)點(diǎn):

執(zhí)行速度非逞镐蹋快废赞,直接使用==進(jìn)行比較要比使用equals()方法快很多;

內(nèi)存占用少叮姑。

雖然intern()方法的優(yōu)點(diǎn)看上去很誘人唉地,但若不是在恰當(dāng)?shù)膱龊现惺褂迷摲椒ǖ脑挘惴堑荒塬@得如此好處传透,反而還可能會(huì)有性能損失耘沼。

下面程序?qū)Ρ攘耸褂胕ntern()方法和未使用intern()方法存儲100萬個(gè)String時(shí)的性能,從輸出結(jié)果可以看出朱盐,若是單純使用intern()方法進(jìn)行數(shù)據(jù)存儲的話群嗤,程序運(yùn)行時(shí)間要遠(yuǎn)高于未使用intern()方法時(shí):

publicclassIntern2{

????????publicstaticvoidmain(String[] args){? ? ? ??

????????????????print("noIntern: "+ noIntern());? ? ? ??

????????????????print("intern: "+ intern());? ??

????????}

? ? ? ? ?privatestaticlongnoIntern(){

????????????????longstart = System.currentTimeMillis();

????????????????for(inti =0; i <1000000; i++) {

????????????????????????intj = i %100;? ? ? ? ? ??

????????????????????????String str = String.valueOf(j);? ? ? ??

? ? ? ? ? ? ? ? ?}

? ? ? ? ? ? ? ? ?returnSystem.currentTimeMillis() - start;??

? ? ? ? ? ?}

? ? ? ? ? ?privatestaticlongintern(){

????????????????????longstart = System.currentTimeMillis();

? ? ? ? ? ? ? ? ? ? ?for(inti =0; i <1000000; i++) {intj = i %100;? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ? ?String str = String.valueOf(j).intern();? ? ? ?

? ? ? ? ? ? ? ? ? ? }

????????????????????returnSystem.currentTimeMillis() - start;? ?

? ? ? ? ? ? ?}

}

//Output:noIntern:48// 未使用intern方法時(shí),存儲100萬個(gè)String所需時(shí)間intern:99

// 使用intern方法時(shí)托享,存儲100萬個(gè)String所需時(shí)間

由于intern()操作每次都需要與常量池中的數(shù)據(jù)進(jìn)行比較以查看常量池中是否存在等值數(shù)據(jù)骚烧,同時(shí)JVM需要確保常量池中的數(shù)據(jù)的唯一性,這就涉及到加鎖機(jī)制闰围,這些操作都是有需要占用CPU時(shí)間的赃绊,所以如果進(jìn)行intern操作的是大量不會(huì)被重復(fù)利用的String的話,則有點(diǎn)得不償失羡榴。由此可見碧查,String.intern()主要適用于只有有限值,并且這些有限值會(huì)被重復(fù)利用的場景校仑,如:數(shù)據(jù)庫表中的列名忠售、人的姓氏、編碼類型等迄沫。

使用 String.intern() 可以保證所有相同內(nèi)容的字符串變量引用相同的內(nèi)存對象稻扬。

intern 用法 intern 適合用在需要讀取數(shù)據(jù)并將這些對象或者字符串納入一個(gè)更大范圍作用域的情況。需要注意的是羊瘩,硬編碼在代碼中的字符串(例如常量等等)都會(huì)被編譯器自動(dòng)的執(zhí)行 intern 操作泰佳。

看一個(gè)例子:

?String city = resultSet.getString(1);

String region = resultSet.getString(2);

String countryCode = resultSet.getString(3);

double city = resultSet.getDouble(4);

double city = resultSet.getDouble(5);

Location location = new Location(city.intern(), region.intern(), countryCode.intern(), long, lat); allLocations.add(location);

所有新創(chuàng)建的地點(diǎn)對象都會(huì)使用 intern 得到的字符串。而從數(shù)據(jù)庫讀取到的臨時(shí)字符串則會(huì)被垃圾回收尘吗。

總結(jié):

String.intern()方法是一種手動(dòng)將字符串加入常量池中的方法逝她,原理如下:如果在常量池中存在與調(diào)用intern()方法的字符串等值的字符串,就直接返回常量池中相應(yīng)字符串的引用睬捶,否則在常量池中復(fù)制一份該字符串黔宛,并將其引用返回(Java7中會(huì)直接在常量池中保存當(dāng)前字符串的引用);

Java 6 中常量池位于PremGen區(qū)擒贸,大小受限臀晃,不建議使用String.intern()方法觉渴,不過Java 7 將常量池移到了Java堆區(qū),大小可控积仗,可以重新考慮使用String.intern()方法疆拘,但是由對比測試可知蜕猫,使用該方法的耗時(shí)不容忽視寂曹,所以需要慎重考慮該方法的使用;

String.intern()方法主要適用于程序中需要保存有限個(gè)會(huì)被反復(fù)使用的值的場景回右,這樣可以減少內(nèi)存消耗隆圆,同時(shí)在進(jìn)行比較操作時(shí)減少時(shí)耗,提高程序性能翔烁。




如何確定 intern 的效率

最好的方法是對整個(gè)堆執(zhí)行一次堆轉(zhuǎn)儲渺氧。堆轉(zhuǎn)儲也會(huì)在發(fā)生 OutOfMemoryError 時(shí)執(zhí)行。

在 MAT (內(nèi)存分析工具蹬屹,譯者注)中打開轉(zhuǎn)儲文件侣背,然后選擇?java.lang.String,依次點(diǎn)擊“Java Basics”慨默、“Group By Value”贩耐。

根據(jù)堆的大小,上面的操作可能耗費(fèi)比較長的時(shí)間厦取。最后可以看到類型這樣的結(jié)果潮太。按 “Retained Heap” 或者是 “Objects” 列進(jìn)行排序,可以發(fā)現(xiàn)一些有趣的東西:

從這快照中我們可以看到虾攻,空的字符串占用了大量的內(nèi)存铡买!兩百萬個(gè)空字符串對象占用了總共 130 MB 的空間。另外可以看到一部分被加載的 JavaScript 腳本霎箍,一些作為鍵的字符串奇钞,它們被用于定位。另外漂坏,還有一些與業(yè)務(wù)邏輯相關(guān)的字符串景埃。

這些與業(yè)務(wù)邏輯相關(guān)的字符串是最容易進(jìn)行 intern 操作的,因?yàn)槲覀兦宄刂浪鼈兪窃谑裁吹胤奖患虞d進(jìn)內(nèi)存的樊拓。對于其他字符串纠亚,可以通過 “Merge shortest Path to GC Root” 選項(xiàng)來找到它們被存儲的位置,這個(gè)信息也許能夠幫助我們找到該使用 intern 的地方筋夏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蒂胞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子条篷,更是在濱河造成了極大的恐慌骗随,老刑警劉巖蛤织,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸿染,居然都是意外死亡指蚜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門涨椒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摊鸡,“玉大人,你說我怎么就攤上這事蚕冬∶饣” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵囤热,是天一觀的道長猎提。 經(jīng)常有香客問我,道長旁蔼,這世上最難降的妖魔是什么锨苏? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮棺聊,結(jié)果婚禮上伞租,老公的妹妹穿的比我還像新娘。我一直安慰自己躺屁,他們只是感情好肯夏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著犀暑,像睡著了一般驯击。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耐亏,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天徊都,我揣著相機(jī)與錄音,去河邊找鬼广辰。 笑死暇矫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的择吊。 我是一名探鬼主播李根,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼几睛!你這毒婦竟也來了房轿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎囱持,沒想到半個(gè)月后夯接,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡纷妆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年盔几,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掩幢。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逊拍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粒蜈,到底是詐尸還是另有隱情顺献,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布枯怖,位于F島的核電站,受9級特大地震影響能曾,放射性物質(zhì)發(fā)生泄漏度硝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一寿冕、第九天 我趴在偏房一處隱蔽的房頂上張望蕊程。 院中可真熱鬧,春花似錦驼唱、人聲如沸藻茂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辨赐。三九已至,卻和暖如春京办,著一層夾襖步出監(jiān)牢的瞬間掀序,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工惭婿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留不恭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓财饥,卻偏偏與公主長得像换吧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子钥星,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容

  • String 的聲明 由 JDK 中關(guān)于String的聲明可以知道: 不同字符串可能共享同一個(gè)底層char數(shù)組沾瓦,例...
    CodeKing2017閱讀 1,638評論 1 2
  • 緣起 開始介紹 intern()方法前,先看一個(gè)簡單的 Java程序吧!下面是一段 Java代碼暴拄,代碼內(nèi)容比較簡單...
    LilacZiyun閱讀 2,733評論 6 17
  • 從網(wǎng)上復(fù)制的漓滔,看別人的比較全面,自己搬過來乖篷,方便以后查找响驴。原鏈接:https://www.cnblogs.com/...
    lxtyp閱讀 1,346評論 0 9
  • String源碼閱讀 wiki 通過反編譯深入理解Java String及intern 成神之路-基礎(chǔ)篇 Java...
    uranusleon閱讀 737評論 0 1
  • 大家可能都知道String.intern()的作用,調(diào)用它時(shí)撕蔼,如果常量池中存在當(dāng)前字符串, 就會(huì)直接返回當(dāng)前字符串...
    biloba閱讀 3,606評論 0 6