java基礎(chǔ):String — 字符串常量池與intern(二)

其他更多java基礎(chǔ)文章:
java基礎(chǔ)學(xué)習(xí)(目錄)


學(xué)習(xí)資料:
String類(lèi)API中文
深入解析String#intern
Java 中new String("字面量") 中 "字面量" 是何時(shí)進(jìn)入字符串常量池的?
new一個(gè)String對(duì)象的時(shí)候锌唾,如果常量池沒(méi)有相應(yīng)的字面量真的會(huì)去它那里創(chuàng)建一個(gè)嗎睛藻?我表示懷疑。

通過(guò)上一篇的學(xué)習(xí)娶眷,我們已經(jīng)了解了String源碼的方法,這一章年叮,我們就通過(guò)Stirng.intern()方法來(lái)延伸锉试,講一下String的其他方面。

字符串字面量

字符串字面量是在 Java?語(yǔ)言規(guī)范的3.10.5. String 字面量中定義的
關(guān)于字面量通俗點(diǎn)解釋就是揍移,使用雙引號(hào)""創(chuàng)建的字符串次和,在堆中創(chuàng)建了對(duì)象后其引用插入到字符串常量池中(jdk1.7后),可以全局使用那伐,遇到相同內(nèi)容的字面量踏施,就不需要再次創(chuàng)建石蔗。舉個(gè)例子:

//這就是創(chuàng)建了一個(gè)aaa字符串字面量
String a = "aaa";
//簡(jiǎn)單來(lái)說(shuō),這就是創(chuàng)建了一個(gè)Stirng對(duì)象和一個(gè)aaa字符串字面量畅形,后面會(huì)詳細(xì)討論
String a = new String("aaa")

字符串常量池

java中常量池的概念主要有三個(gè):全局字符串常量池养距,class文件常量池運(yùn)行時(shí)常量池日熬。我們現(xiàn)在所說(shuō)的就是全局字符串常量池棍厌,在下文中可能會(huì)簡(jiǎn)稱(chēng)常量池。對(duì)這個(gè)想弄明白的同學(xué)可以看這篇Java中幾種常量池的區(qū)分碍遍。

字符串常量池里面存的到底是對(duì)象定铜,還是引用呢?我查了很多資料怕敬,最后根據(jù)自己的測(cè)試和查到的各種說(shuō)法揣炕,認(rèn)為在jdk1.7后字符串常量池中存的是引用。在new一個(gè)String對(duì)象的時(shí)候东跪,如果常量池沒(méi)有相應(yīng)的字面量真的會(huì)去它那里創(chuàng)建一個(gè)嗎畸陡?我表示懷疑。問(wèn)題中虽填,R大的回答解答了我:

至于說(shuō):
之前一直有個(gè)結(jié)論就是:當(dāng)創(chuàng)建一個(gè)string對(duì)象的時(shí)候丁恭,去字符串常量池看是否有相應(yīng)的字面量,如果沒(méi)有就創(chuàng)建一個(gè)斋日。
這個(gè)說(shuō)法從來(lái)都不正確牲览。
對(duì)象在堆里。常量池存引用恶守。

這個(gè)字符串常量池的位置也是隨著jdk版本的不同而位置不同第献。在jdk6中,常量池的位置在永久代(方法區(qū))中兔港,此時(shí)常量池中存儲(chǔ)的是對(duì)象庸毫。在jdk7中,常量池的位置在堆中衫樊,此時(shí)飒赃,常量池存儲(chǔ)的就是引用了。在jdk8中科侈,永久代(方法區(qū))被元空間取代了载佳。這里就引出了一個(gè)很常見(jiàn)很經(jīng)典的問(wèn)題,看下面這段代碼臀栈。

    @Test
    public void test(){
        String s = new String("2");
        s.intern();
        String s2 = "2";
        System.out.println(s == s2);


        String s3 = new String("3") + new String("3");
        s3.intern();
        String s4 = "33";
        System.out.println(s3 == s4);
    }

jdk6
false
false

jdk7
false
true

這段代碼在jdk6中輸出是false false蔫慧,但是在jdk7中輸出的是false true。我們通過(guò)圖來(lái)一行行解釋挂脑。

JDK1.6代碼圖

JDK1.6
String s = new String("2");創(chuàng)建了兩個(gè)對(duì)象藕漱,一個(gè)在堆中的StringObject對(duì)象,一個(gè)是在常量池中的“2”對(duì)象崭闲。
s.intern();在常量池中尋找與s變量?jī)?nèi)容相同的對(duì)象肋联,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對(duì)象“2”,返回對(duì)象2的地址刁俭。
String s2 = "2";使用字面量創(chuàng)建橄仍,在常量池尋找是否有相同內(nèi)容的對(duì)象,發(fā)現(xiàn)有牍戚,返回對(duì)象"2"的地址侮繁。
System.out.println(s == s2);從上面可以分析出,s變量和s2變量地址指向的是不同的對(duì)象如孝,所以返回false

String s3 = new String("3") + new String("3");創(chuàng)建了兩個(gè)對(duì)象宪哩,一個(gè)在堆中的StringObject對(duì)象,一個(gè)是在常量池中的“3”對(duì)象第晰。中間還有2個(gè)匿名的new String("3")我們不去討論它們锁孟。
s3.intern();在常量池中尋找與s3變量?jī)?nèi)容相同的對(duì)象,沒(méi)有發(fā)現(xiàn)“33”對(duì)象茁瘦,在常量池中創(chuàng)建“33”對(duì)象品抽,返回“33”對(duì)象的地址。
String s4 = "33";使用字面量創(chuàng)建甜熔,在常量池尋找是否有相同內(nèi)容的對(duì)象圆恤,發(fā)現(xiàn)有,返回對(duì)象"33"的地址腔稀。
System.out.println(s3 == s4);從上面可以分析出盆昙,s3變量和s4變量地址指向的是不同的對(duì)象,所以返回false

JDK1.7代碼圖

JDK1.7
String s = new String("2");創(chuàng)建了兩個(gè)對(duì)象烧颖,一個(gè)在堆中的StringObject對(duì)象弱左,一個(gè)是在堆中的“2”對(duì)象,并在常量池中保存“2”對(duì)象的引用地址炕淮。
s.intern();在常量池中尋找與s變量?jī)?nèi)容相同的對(duì)象拆火,發(fā)現(xiàn)已經(jīng)存在內(nèi)容相同對(duì)象“2”,返回對(duì)象“2”的引用地址涂圆。
String s2 = "2";使用字面量創(chuàng)建们镜,在常量池尋找是否有相同內(nèi)容的對(duì)象,發(fā)現(xiàn)有润歉,返回對(duì)象“2”的引用地址模狭。
System.out.println(s == s2);從上面可以分析出,s變量和s2變量地址指向的是不同的對(duì)象踩衩,所以返回false

String s3 = new String("3") + new String("3");創(chuàng)建了兩個(gè)對(duì)象嚼鹉,一個(gè)在堆中的StringObject對(duì)象贩汉,一個(gè)是在堆中的“3”對(duì)象,并在常量池中保存“3”對(duì)象的引用地址锚赤。中間還有2個(gè)匿名的new String("3")我們不去討論它們匹舞。
s3.intern();在常量池中尋找與s3變量?jī)?nèi)容相同的對(duì)象,沒(méi)有發(fā)現(xiàn)“33”對(duì)象线脚,將s3對(duì)應(yīng)的StringObject對(duì)象的地址保存到常量池中赐稽,返回StringObject對(duì)象的地址。
String s4 = "33";使用字面量創(chuàng)建浑侥,在常量池尋找是否有相同內(nèi)容的對(duì)象姊舵,發(fā)現(xiàn)有,返回其地址寓落,也就是StringObject對(duì)象的引用地址括丁。
System.out.println(s3 == s4);從上面可以分析出,s3變量和s4變量地址指向的是相同的對(duì)象零如,所以返回true躏将。

再來(lái)一段變種代碼
通過(guò)上面的逐句分析,應(yīng)該都了解了為什么兩個(gè)版本的jdk返回值會(huì)不一樣了考蕾。那我們稍稍改變一下上面代碼中的語(yǔ)句順序祸憋,將intern方法與字面量賦值語(yǔ)句調(diào)換順序:

        String s = new String("2");
        String s2 = "2";
        s.intern();
        System.out.println(s == s2);

        String s3 = new String("3") + new String("3");
        String s4 = "33";
        s3.intern();
        System.out.println(s3 == s4);

答案是多少呢,大家可以稍微思考一下再往下看:

jdk6
false
false

jdk7
false
false

原理很簡(jiǎn)單肖卧,因?yàn)樵谡{(diào)用intern方法前蚯窥,先使用了字面量賦值語(yǔ)句,所以在常量池中都存在了與變量相同內(nèi)容的對(duì)象(jdk1.6)或?qū)ο蟮囊茫╦dk1.7+)塞帐,此時(shí)再調(diào)用intern方法拦赠,就會(huì)發(fā)現(xiàn)常量池里的對(duì)象地址和變量的地址不是指向同一個(gè)對(duì)象,自然就false了葵姥。對(duì)于這段不懂的同學(xué)可以評(píng)論荷鼠,我看需不需要再畫(huà)一次結(jié)構(gòu)圖和逐句解釋。

字面量是何時(shí)進(jìn)入常量池

通過(guò)上面兩段代碼榔幸,我們發(fā)現(xiàn)調(diào)用intern方法和字面量賦值的順序是很重要的允乐。我們將上面兩段代碼都通過(guò)javap命令查看其字節(jié)碼,發(fā)現(xiàn)在class類(lèi)常量池中都有“33”削咆。這說(shuō)明在運(yùn)行時(shí)牍疏,class常量池里的常量并不會(huì)直接全部加入到全局常量池中,那這是在什么時(shí)候加入的呢拨齐?我搜到了下面大神的回答
new String(“字面量”) 中 “字面量” 是何時(shí)進(jìn)入字符串常量池的?

簡(jiǎn)單來(lái)說(shuō):

  • HotSpot VM的實(shí)現(xiàn)來(lái)說(shuō)起惕,加載類(lèi)的時(shí)候荠割,那些字符串字面量會(huì)進(jìn)入到當(dāng)前類(lèi)的運(yùn)行時(shí)常量池坡贺,不會(huì)進(jìn)入全局的字符串常量池 ;

  • 在字面量賦值的時(shí)候,會(huì)翻譯成字節(jié)碼ldc指令援岩,ldc指令觸發(fā)lazy resolution動(dòng)作

    • 到當(dāng)前類(lèi)的運(yùn)行時(shí)常量池(runtime constant pool,HotSpot VM里是ConstantPool + ConstantPoolCache)去查找該index對(duì)應(yīng)的項(xiàng)
    • 如果該項(xiàng)尚未resolve則resolve之掏导,并返回resolve后的內(nèi)容窄俏。
    • 在遇到String類(lèi)型常量時(shí),resolve的過(guò)程如果發(fā)現(xiàn)StringTable已經(jīng)有了內(nèi)容匹配的java.lang.String的引用碘菜,則直接返回這個(gè)引用;
    • 如果StringTable里尚未有內(nèi)容匹配的String實(shí)例的引用,則會(huì)在Java堆里創(chuàng)建一個(gè)對(duì)應(yīng)內(nèi)容的String對(duì)象限寞,然后在StringTable記錄下這個(gè)引用忍啸,并返回這個(gè)引用出去。

String“+”符號(hào)的實(shí)現(xiàn)

在我們使用中經(jīng)常會(huì)用到+符號(hào)來(lái)拼接字符串履植,但是這個(gè)+符號(hào)在String中的實(shí)現(xiàn)還是有講究的计雌。如果是相加含有String對(duì)象,則底部是使用StringBuilder實(shí)現(xiàn)的拼接的

String str1 ="str1";
String str2 ="str2";
String str3 = str1 + str2;

如果相加的參數(shù)只有字面量或者常量或基礎(chǔ)類(lèi)型變量玫霎,則會(huì)直接編譯為拼接后的字符串凿滤。

String str1 =1+"str2"+"str3";

這里有個(gè)小細(xì)節(jié)
如果使用字面量拼接的話庶近,java常量池里是不會(huì)保存拼接的參數(shù)的翁脆,而是直接編譯成拼接后的字符串保存,我們看看這段代碼:

        String str1 = new String("aa"+"bb");
        //String str3 = "aa";
        String str2 = new StringBuilder("a").append("a").toString();
        System.out.println(str2==str2.intern());

這段代碼的輸出是true鼻种》捶可以得知,在str1變量的創(chuàng)建中叉钥,雖然我們用了字面量“aa”罢缸,但是我們常量池里并沒(méi)有aa,所以str2==str.intern()才會(huì)返回true投队。如果我們?nèi)サ魋tr3的注釋?zhuān)匦逻\(yùn)行枫疆,就會(huì)輸出false

個(gè)人疑問(wèn)

我在學(xué)習(xí)的過(guò)程中敷鸦,遇到了一個(gè)疑問(wèn)息楔,怎么都查不到是為什么,大家如果看到這里轧膘,可以順手寫(xiě)一下這段代碼钞螟,看是不是也會(huì)遇到這樣的問(wèn)題。

public static void main(String[] args){
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
    }
   @Test
    public void test7(){
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);
    }

如上所示谎碍,分別在test環(huán)境和main方法里運(yùn)行相同代碼鳞滨,此時(shí)main函數(shù)里返回true,test環(huán)境下卻是返回false蟆淀。按邏輯這里應(yīng)該是返回true才對(duì)拯啦。但是我測(cè)試了將參數(shù)“1”改為“2“”或者“3”澡匪,兩者返回的都是true。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末褒链,一起剝皮案震驚了整個(gè)濱河市唁情,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甫匹,老刑警劉巖甸鸟,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異兵迅,居然都是意外死亡抢韭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)恍箭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)刻恭,“玉大人,你說(shuō)我怎么就攤上這事扯夭△⒓郑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵交洗,是天一觀的道長(zhǎng)骑科。 經(jīng)常有香客問(wèn)我,道長(zhǎng)构拳,這世上最難降的妖魔是什么纵散? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮隐圾,結(jié)果婚禮上伍掀,老公的妹妹穿的比我還像新娘。我一直安慰自己暇藏,他們只是感情好蜜笤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著盐碱,像睡著了一般把兔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓮顽,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天县好,我揣著相機(jī)與錄音,去河邊找鬼暖混。 笑死缕贡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晾咪,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼收擦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谍倦?” 一聲冷哼從身側(cè)響起塞赂,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昼蛀,沒(méi)想到半個(gè)月后宴猾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叼旋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年鳍置,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片送淆。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怕轿,靈堂內(nèi)的尸體忽然破棺而出偷崩,到底是詐尸還是另有隱情,我是刑警寧澤撞羽,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布阐斜,位于F島的核電站,受9級(jí)特大地震影響诀紊,放射性物質(zhì)發(fā)生泄漏谒出。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一邻奠、第九天 我趴在偏房一處隱蔽的房頂上張望笤喳。 院中可真熱鬧,春花似錦碌宴、人聲如沸杀狡。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)呜象。三九已至,卻和暖如春碑隆,著一層夾襖步出監(jiān)牢的瞬間恭陡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工上煤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留休玩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哥捕,于是被迫代替她去往敵國(guó)和親牧抽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • 從網(wǎng)上復(fù)制的遥赚,看別人的比較全面扬舒,自己搬過(guò)來(lái),方便以后查找凫佛。原鏈接:https://www.cnblogs.com/...
    lxtyp閱讀 1,344評(píng)論 0 9
  • 字符串常量池 首先讲坎,記錄一點(diǎn): 字符串池的確切位置沒(méi)有被指定,并且可以從一個(gè)JVM實(shí)現(xiàn)到另一個(gè)不同愧薛。值得注意的是晨炕,...
    LcYYYYYYYY閱讀 1,082評(píng)論 0 0
  • 此時(shí),吳平的能量及境界雖然已經(jīng)達(dá)到很高的程度毫炉,在炎夢(mèng)城中也幾乎算是一個(gè)高手了瓮栗,但是卻僅僅修煉了碎石拳這一門(mén)功法...
    玠炎閱讀 329評(píng)論 0 2
  • 雜篇之七《盜跖》、之九《漁父》 《盜跖》共分三章瞄勾,《漁父》共分兩章费奸,因?yàn)橹髦级际桥険羧寮胰柿x禮教思想對(duì)社會(huì)的毒害,...
    安歌林閱讀 848評(píng)論 0 0
  • 周末同學(xué)聚餐进陡。已婚已育中年女性的話題無(wú)外乎孩子養(yǎng)育和吐槽婆媳關(guān)系愿阐。同學(xué)小艾是大家羨慕的對(duì)象,她嫁了個(gè)有錢(qián)人趾疚,不用操...
    火花danjuan閱讀 333評(píng)論 0 1