我終于搞清楚了和String有關(guān)的那點(diǎn)事兒嘉裤。

String,是Java中除了基本數(shù)據(jù)類型以外栖博,最為重要的一個(gè)類型了屑宠。很多人會(huì)認(rèn)為他比較簡(jiǎn)單。但是和String有關(guān)的面試題有很多仇让,下面我隨便找兩道面試題典奉,看看你能不能都答對(duì):

Q1:String s = new String("hollis");定義了幾個(gè)對(duì)象。

Q2:如何理解String的intern方法

上面這兩個(gè)是面試題和String相關(guān)的比較成ミ矗考的卫玖,很多人一般都知道答案。

A1:若常量池中已經(jīng)存在"hollis"踊淳,則直接引用假瞬,也就是此時(shí)只會(huì)創(chuàng)建一個(gè)對(duì)象陕靠,如果常量池中不存在"hollis",則先創(chuàng)建后引用脱茉,也就是有兩個(gè)剪芥。

A2:當(dāng)一個(gè)String實(shí)例str調(diào)用intern()方法時(shí),Java查找常量池中是否有相同Unicode的字符串常量琴许,如果有税肪,則返回其的引用,如果沒有榜田,則在常量池中增加一個(gè)Unicode等于str的字符串并返回它的引用益兄;

兩個(gè)答案看上去沒有任何問題,但是串慰,仔細(xì)想想好像哪里不對(duì)呀偏塞。按照上面的兩個(gè)面試題的回答,就是說new String也會(huì)檢查常量池邦鲫,如果有的話就直接引用灸叼,如果不存在就要在常量池創(chuàng)建一個(gè),那么還要intern干啥庆捺?難道以下代碼是沒有意義的嗎古今?

Strings =newString("Hollis").intern();

如果,每當(dāng)我們使用new創(chuàng)建字符串的時(shí)候滔以,都會(huì)到字符串池檢查捉腥,然后返回。那么以下代碼也應(yīng)該輸出結(jié)果都是true?

String s1 ="Hollis";

String s2 =newString("Hollis");

String s3 =newString("Hollis").intern();

? ? System.out.println(s1 == s2);

? ? System.out.println(s1 == s3);

但是你画,以上代碼輸出結(jié)果為(base jdk1.8.0_73):

false

true

不知道抵碟,聰明的讀者看完這段代碼之后,是不是有點(diǎn)被搞蒙了坏匪,到底是怎么回事兒拟逮?

別急,且聽我慢慢道來适滓。

字面量和運(yùn)行時(shí)常量池

JVM為了提高性能和減少內(nèi)存開銷敦迄,在實(shí)例化字符串常量的時(shí)候進(jìn)行了一些優(yōu)化。為了減少在JVM中創(chuàng)建的字符串的數(shù)量凭迹,字符串類維護(hù)了一個(gè)字符串常量池罚屋。

在JVM運(yùn)行時(shí)區(qū)域的方法區(qū)中,有一塊區(qū)域是運(yùn)行時(shí)常量池嗅绸,主要用來存儲(chǔ)編譯期生成的各種字面量和符號(hào)引用脾猛。

了解Class文件結(jié)構(gòu)或者做過Java代碼的反編譯的朋友可能都知道,在java代碼被javac編譯之后鱼鸠,文件結(jié)構(gòu)中是包含一部分Constant pool的尖滚。比如以下代碼:

publicstaticvoidmain(String[] args){

String s ="Hollis";

}

經(jīng)過編譯后喉刘,常量池內(nèi)容如下:

Constant pool:

#1= Methodref#4.#20// java/lang/Object."":()V

#2=String#21// Hollis

#3=Class#22// StringDemo

#4=Class#23// java/lang/Object

? ...

#16= Utf8? ? ? ? ? ? ? s

? ..

#21= Utf8? ? ? ? ? ? ? Hollis

#22= Utf8? ? ? ? ? ? ? StringDemo

#23= Utf8? ? ? ? ? ? ? java/lang/Object

上面的Class文件中的常量池中,比較重要的幾個(gè)內(nèi)容:

#16 = Utf8? ? ? ? ? ? ? s

#21 = Utf8? ? ? ? ? ? ? Hollis

#22 = Utf8? ? ? ? ? ? ? StringDemo

上面幾個(gè)常量中漆弄,s就是前面提到的符號(hào)引用睦裳,而Hollis就是前面提到的字面量。而Class文件中的常量池部分的內(nèi)容撼唾,會(huì)在運(yùn)行期被運(yùn)行時(shí)常量池加載進(jìn)去廉邑。關(guān)于字面量,詳情參考Java SE Specifications

new String創(chuàng)建了幾個(gè)對(duì)象

下面倒谷,我們可以來分析下String s = new String("Hollis");創(chuàng)建對(duì)象情況了蛛蒙。

這段代碼中,我們可以知道的是渤愁,在編譯期牵祟,符號(hào)引用s和字面量Hollis會(huì)被加入到Class文件的常量池中,然后在類加載階段(具體時(shí)間段參考Java 中new String("字面量") 中 "字面量" 是何時(shí)進(jìn)入字符串常量池的?)抖格,這兩個(gè)常量會(huì)進(jìn)入常量池诺苹。

但是,這個(gè)“進(jìn)入”階段雹拄,并不會(huì)直接把所有類中定義的常量全部都加載進(jìn)來收奔,而是會(huì)做個(gè)比較,如果需要加到字符串常量池中的字符串已經(jīng)存在滓玖,那么就不需要再把字符串字面量加載進(jìn)來了坪哄。

所以,當(dāng)我們說<若常量池中已經(jīng)存在"hollis"势篡,則直接引用翩肌,也就是此時(shí)只會(huì)創(chuàng)建一個(gè)對(duì)象>說的就是這個(gè)字符串字面量在字符串池中被創(chuàng)建的過程。

說完了編譯期的事兒了禁悠,該到運(yùn)行期了念祭,在運(yùn)行期,new String("Hollis");執(zhí)行到的時(shí)候绷蹲,是要在Java堆中創(chuàng)建一個(gè)字符串對(duì)象的,而這個(gè)對(duì)象所對(duì)應(yīng)的字符串字面量是保存在字符串常量池中的顾孽。但是祝钢,String s = new String("Hollis");,對(duì)象的符號(hào)引用s是保存在Java虛擬機(jī)棧上的若厚,他保存的是堆中剛剛創(chuàng)建出來的的字符串對(duì)象的引用拦英。

所以,你也就知道以下代碼輸出結(jié)果為false的原因了测秸。

Strings1 =newString("Hollis");

Strings2 =newString("Hollis");

System.out.println(s1 == s2);

因?yàn)椋?=比較的是s1和s2在堆中創(chuàng)建的對(duì)象的地址疤估,當(dāng)然不同了灾常。但是如果使用equals,那么比較的就是字面量的內(nèi)容了铃拇,那就會(huì)得到true钞瀑。

在不同版本的JDK中,Java堆和字符串常量池之間的關(guān)系也是不同的慷荔,這里為了方便表述雕什,就畫成兩個(gè)獨(dú)立的物理區(qū)域了。具體情況請(qǐng)參考Java虛擬機(jī)規(guī)范显晶。

所以贷岸,String s = new String("Hollis");創(chuàng)建幾個(gè)對(duì)象的答案你也就清楚了。

常量池中的“對(duì)象”是在編譯期就確定好了的磷雇,在類被加載的時(shí)候創(chuàng)建的偿警,如果類加載時(shí),該字符串常量在常量池中已經(jīng)有了唯笙,那這一步就省略了螟蒸。堆中的對(duì)象是在運(yùn)行期才確定的,在代碼執(zhí)行到new的時(shí)候創(chuàng)建的睁本。

運(yùn)行時(shí)常量池的動(dòng)態(tài)擴(kuò)展

編譯期生成的各種字面量和符號(hào)引用是運(yùn)行時(shí)常量池中比較重要的一部分來源尿庐,但是并不是全部。那么還有一種情況呢堰,可以在運(yùn)行期像運(yùn)行時(shí)常量池中增加常量抄瑟。那就是String的intern方法。

當(dāng)一個(gè)String實(shí)例調(diào)用intern()方法時(shí)枉疼,Java查找常量池中是否有相同Unicode的字符串常量皮假,如果有,則返回其的引用骂维,如果沒有惹资,則在常量池中增加一個(gè)Unicode等于str的字符串并返回它的引用;

intern()有兩個(gè)作用航闺,第一個(gè)是將字符串字面量放入常量池(如果池沒有的話)褪测,第二個(gè)是返回這個(gè)常量的引用。

我們?cè)賮砜聪麻_頭的那個(gè)讓人產(chǎn)生疑惑的例子:

Strings1 ="Hollis";

Strings2 =newString("Hollis");

Strings3 =newString("Hollis").intern();

? ? System.out.println(s1 == s2);

? ? System.out.println(s1 == s3);

你可以簡(jiǎn)單的理解為String s1 = "Hollis";和String s3 = new String("Hollis").intern();做的事情是一樣的(但實(shí)際有些區(qū)別潦刃,這里暫不展開)侮措。都是定義一個(gè)字符串對(duì)象,然后將其字符串字面量保存在常量池中乖杠,并把這個(gè)字面量的引用返回給定義好的對(duì)象引用分扎。

對(duì)于String s3 = new String("Hollis").intern();,在不調(diào)用intern情況胧洒,s3指向的是JVM在堆中創(chuàng)建的那個(gè)對(duì)象的引用的(如圖中的s2)畏吓。但是當(dāng)執(zhí)行了intern方法時(shí)墨状,s3將指向字符串常量池中的那個(gè)字符串常量。

由于s1和s3都是字符串常量池中的字面量的引用菲饼,所以s1==s3肾砂。但是,s2的引用是堆中的對(duì)象巴粪,所以s2!=s1通今。

intern的正確用法

不知道,你有沒有發(fā)現(xiàn)肛根,在String s3 = new String("Hollis").intern();中辫塌,其實(shí)intern是多余的?

因?yàn)榫退悴挥胕ntern派哲,Hollis作為一個(gè)字面量也會(huì)被加載到Class文件的常量池臼氨,進(jìn)而加入到運(yùn)行時(shí)常量池中,為啥還要多此一舉呢芭届?到底什么場(chǎng)景下才需要使用intern呢储矩?

在解釋這個(gè)之前,我們先來看下以下代碼:

Strings1 ="Hollis";

Strings2 ="Chuang";

Strings3 = s1 + s2;

Strings4 ="Hollis"+"Chuang";

在經(jīng)過反編譯后褂乍,得到代碼如下:

Strings1 ="Hollis";

Strings2 ="Chuang";

Strings3 = (newStringBuilder()).append(s1).append(s2).toString();

Strings4 ="HollisChuang";

可以發(fā)現(xiàn)持隧,同樣是字符串拼接,s3和s4在經(jīng)過編譯器編譯后的實(shí)現(xiàn)方式并不一樣逃片。s3被轉(zhuǎn)化成StringBuilder及append屡拨,而s4被直接拼接成新的字符串。

如果你感興趣褥实,你還能發(fā)現(xiàn)呀狼,String s3 = s1 + s2;經(jīng)過編譯之后,常量池中是有兩個(gè)字符串常量的分別是Hollis损离、Chuang(其實(shí)Hollis和Chuang是String s1 = "Hollis";和String s2 = "Chuang";定義出來的)哥艇,拼接結(jié)果HollisChuang并不在常量池中。

如果代碼只有String s4 = "Hollis" + "Chuang";僻澎,那么常量池中將只有HollisChuang而沒有"Hollis" 和 "Chuang"貌踏。

究其原因,是因?yàn)槌A砍匾4娴氖且汛_定的字面量值窟勃。也就是說祖乳,對(duì)于字符串的拼接,純字面量和字面量的拼接拳恋,會(huì)把拼接結(jié)果作為常量保存到字符串凡资。

如果在字符串拼接中砸捏,有一個(gè)參數(shù)是非字面量谬运,而是一個(gè)變量的話隙赁,整個(gè)拼接操作會(huì)被編譯成StringBuilder.append,這種情況編譯器是無法知道其確定值的梆暖。只有在運(yùn)行期才能確定伞访。

那么,有了這個(gè)特性了轰驳,intern就有用武之地了厚掷。那就是很多時(shí)候,我們?cè)诔绦蛑械玫降淖址侵挥性谶\(yùn)行期才能確定的级解,在編譯期是無法確定的冒黑,那么也就沒辦法在編譯期被加入到常量池中。

這時(shí)候勤哗,對(duì)于那種可能經(jīng)常使用的字符串抡爹,使用intern進(jìn)行定義,每次JVM運(yùn)行到這段代碼的時(shí)候芒划,就會(huì)直接把常量池中該字面值的引用返回冬竟,這樣就可以減少大量字符串對(duì)象的創(chuàng)建了。

如一深入解析String#intern文中舉的一個(gè)例子:

staticfinalintMAX =1000*10000;

staticfinalString[] arr =newString[MAX];

publicstaticvoidmain(String[] args)throwsException{

Integer[] DB_DATA =newInteger[10];

Random random =newRandom(10*10000);

for(inti =0; i < DB_DATA.length; i++) {

? ? ? ? DB_DATA[i] = random.nextInt();

? ? }

longt = System.currentTimeMillis();

for(inti =0; i < MAX; i++) {

arr[i] =newString(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();

? ? }

System.out.println((System.currentTimeMillis() - t) +"ms");

? ? System.gc();

}

在以上代碼中民逼,我們明確的知道泵殴,會(huì)有很多重復(fù)的相同的字符串產(chǎn)生,但是這些字符串的值都是只有在運(yùn)行期才能確定的拼苍。所以笑诅,只能我們通過intern顯示的將其加入常量池,這樣可以減少很多字符串的重復(fù)創(chuàng)建映屋。

總結(jié)

我們?cè)倩氐轿恼麻_頭那個(gè)疑惑:按照上面的兩個(gè)面試題的回答苟鸯,就是說new String也會(huì)檢查常量池,如果有的話就直接引用棚点,如果不存在就要在常量池創(chuàng)建一個(gè)早处,那么還要intern干啥?難道以下代碼是沒有意義的嗎瘫析?

Strings =newString("Hollis").intern();

而intern中說的“如果有的話就直接返回其引用”砌梆,指的是會(huì)把字面量對(duì)象的引用直接返回給定義的對(duì)象。這個(gè)過程是不會(huì)在Java堆中再創(chuàng)建一個(gè)String對(duì)象的贬循。

的確咸包,以上代碼的寫法其實(shí)是使用intern是沒什么意義的。因?yàn)樽置媪縃ollis會(huì)作為編譯期常量被加載到運(yùn)行時(shí)常量池杖虾。

之所以能有以上的疑惑烂瘫,其實(shí)是對(duì)字符串常量池、字面量等概念沒有真正理解導(dǎo)致的。有些問題其實(shí)就是這樣坟比,單個(gè)問題芦鳍,自己都知道答案,但是多個(gè)問題綜合到一起就蒙了葛账。歸根結(jié)底是知識(shí)的理解還停留在點(diǎn)上柠衅,沒有串成面。

歡迎各位同學(xué)關(guān)注拓薪教育,與眾多Java開發(fā)人員交流技術(shù)心得!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末籍琳,一起剝皮案震驚了整個(gè)濱河市菲宴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趋急,老刑警劉巖喝峦,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呜达,居然都是意外死亡愈犹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門闻丑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漩怎,“玉大人,你說我怎么就攤上這事嗦嗡⊙福” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵侥祭,是天一觀的道長(zhǎng)叁执。 經(jīng)常有香客問我,道長(zhǎng)矮冬,這世上最難降的妖魔是什么谈宛? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮胎署,結(jié)果婚禮上吆录,老公的妹妹穿的比我還像新娘。我一直安慰自己琼牧,他們只是感情好恢筝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巨坊,像睡著了一般撬槽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上趾撵,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天侄柔,我揣著相機(jī)與錄音,去河邊找鬼。 笑死暂题,一個(gè)胖子當(dāng)著我的面吹牛勋磕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敢靡,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼苦银!你這毒婦竟也來了啸胧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤幔虏,失蹤者是張志新(化名)和其女友劉穎纺念,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體想括,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陷谱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟蜈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烟逊。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铺根,靈堂內(nèi)的尸體忽然破棺而出宪躯,到底是詐尸還是另有隱情,我是刑警寧澤位迂,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布访雪,位于F島的核電站,受9級(jí)特大地震影響掂林,放射性物質(zhì)發(fā)生泄漏臣缀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一泻帮、第九天 我趴在偏房一處隱蔽的房頂上張望精置。 院中可真熱鬧,春花似錦锣杂、人聲如沸氯窍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狼讨。三九已至,卻和暖如春柒竞,著一層夾襖步出監(jiān)牢的瞬間政供,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留布隔,地道東北人离陶。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像衅檀,于是被迫代替她去往敵國(guó)和親招刨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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

  • 前言 RTFSC (Read the fucking source code )才是生活中最重要的哀军。我們天天就是要...
    二毛_coder閱讀 455評(píng)論 1 1
  • ??需要說明的一點(diǎn)是,這篇文章是以《深入理解Java虛擬機(jī)》第二版這本書為基礎(chǔ)的杉适,這里假設(shè)大家已經(jīng)了解了JVM的運(yùn)...
    Geeks_Liu閱讀 14,015評(píng)論 5 44
  • 注:都是在百度搜索整理的答案谎倔,如有侵權(quán)和錯(cuò)誤,希告知更改猿推。 一片习、哪些情況下的對(duì)象會(huì)被垃圾回收機(jī)制處理掉 ?當(dāng)對(duì)象對(duì)...
    Jenchar閱讀 3,224評(píng)論 3 2
  • 可是卻沒有,我多么想你問我是不是生氣了蹬叭,我說沒有藕咏,可是你沒有。 或許你覺得我習(xí)慣了這樣被你罵的生活秽五。 我告訴你...
    HS安林閱讀 144評(píng)論 0 0
  • =01 城市的黃昏來得格外的早筝蚕,等了很久的13路公交車搖搖晃晃的停在站臺(tái)前卦碾,言夢(mèng)抬頭看了一眼,全都是面目漠然的乘客...
    是余音啊閱讀 1,350評(píng)論 25 24