字符串常量池理解

在JVM中系任,為了減少字符串對象的重復創(chuàng)建,維護了一塊特殊的內存空間虐块,這塊內存就被稱為字符串常量池俩滥。

在JDK1.6及之前,字符串常量池存放在方法區(qū)中贺奠。到JDK1.7之后霜旧,就從方法區(qū)中移除了,而存放在堆中儡率。以下是《深入理解Java虛擬機》第二版原文:

對于HotSpot虛擬機挂据,根據官方發(fā)布的路線圖信息,現在也有放棄永久代并逐步改為采用Native Memory來實現方法區(qū)的規(guī)劃了儿普,在目前已經發(fā)布的JDK1.7 的HotSpot中崎逃,已經把原本放在永久代的字符串常量池移出。

我們知道字符串常量一般有兩種創(chuàng)建方式:

  1. 使用字符串字面量定義
String s = "aa";
  1. 通過new創(chuàng)建字符串對象
String s = new String("aa");

那這兩種方式有什么區(qū)別呢眉孩?

第一種方式通過字面量定義一個字符串時个绍,JVM會先去字符串常量池中檢查是否存在“aa”這個對象。如果不存在浪汪,則在字符串常量池中創(chuàng)建“aa”對象障贸,并將引用返回給s,這樣s的引用就指向字符串常量池中的“aa”對象吟宦。如果存在篮洁,則不創(chuàng)建任何對象,直接把常量池中“aa”對象的地址返回殃姓,賦值給s袁波。

第二種方式通過new關鍵字創(chuàng)建一個字符串時瓦阐,我們需要知道創(chuàng)建了幾個對象,這也是面試中經常問到的篷牌。首先睡蟋,會在字符串常量池中創(chuàng)建一個"aa"對象。然后執(zhí)行new String時會在堆中創(chuàng)建一個“aa”的對象枷颊,然后把s的引用指向堆中的這個“aa”對象戳杀。

思考以下代碼的打印結果:

public class StringTest {
    public static void main(String[] args) {
        //創(chuàng)建了兩個對象,一份存在字符串常量池中夭苗,一份存在堆中
        String s = new String("aa");
        //檢查常量池中是否存在字符串aa信卡,此處存在則直接返回
        String s1 = s.intern();
        String s2 = "aa";

        System.out.println(s == s2);  //①
        System.out.println(s1 == s2); //②

        String s3 = new String("b") + new String("b");
        //常量池中沒有bb,在jdk1.7之后會把堆中的引用放到常量池中题造,故引用地址相等
        String s4 = s3.intern();
        String s5 = "bb";

        System.out.println(s3 == s5 ); //③
        System.out.println(s4 == s5);  //④

    }
}

以上的①②③④四個地方應該輸出true還是false呢傍菇?別著急,先看下界赔,代碼中用到了intern方法丢习。這個方法的作用是,在運行期間可以把新的常量放入到字符串常量池中淮悼。

看下String源碼中對intern方法的解釋:

file

字面意思就是咐低,當調用這個方法時,會去檢查字符串常量池中是否已經存在這個字符串袜腥,如果存在的話见擦,就直接返回,如果不存在的話瞧挤,就把這個字符串常量加入到字符串常量池中,然后再返回其引用儡湾。

但是特恬,其實在JDK1.6和 JDK1.7的處理方式是有一些不同的。

在JDK1.6中徐钠,如果字符串常量池中已經存在該字符串對象癌刽,則直接返回池中此字符串對象的引用。否則尝丐,將此字符串的對象添加到字符串常量池中显拜,然后返回該字符串對象的引用。

在JDK1.7中爹袁,如果字符串常量池中已經存在該字符串對象远荠,則返回池中此字符串對象的引用。否則失息,如果堆中已經有這個字符串對象了譬淳,則把此字符串對象的引用添加到字符串常量池中并返回該引用档址,如果堆中沒有此字符串對象,則先在堆中創(chuàng)建字符串對象邻梆,再返回其引用守伸。(這也說明,此時字符串常量池中存儲的是對象的引用浦妄,而對象本身存儲于堆中)

于是代碼中尼摹,String s = new String("aa");創(chuàng)建了兩個“aa”對象,一個存在字符串常量池中剂娄,一個存在堆中蠢涝。

String s1 = s.intern(); 由于字符串常量池中已經存在“aa”對象,于是直接返回其引用宜咒,故s1指向字符串常量池中的對象惠赫。

String s2 = "aa"; 此時字符串常量池中已經存在“aa”對象,所以也直接返回故黑,故 s2和 s1的地址相同儿咱。②返回true。

System.out.println(s == s2); 由于s的引用指向的是堆中的“aa”對象场晶,s2指向的是常量池中的對象混埠。故不相等,①返回false诗轻。

String s3 = new String("b") + new String("b"); 先說明一下钳宪,這種形式的字符串拼接,等同于使用StringBuilder的append方法把兩個“b”拼接扳炬,然后調用toString方法吏颖,new出“bb”對象,因此“bb”對象是在堆中生成的恨樟。所以半醉,這段代碼最終生成了兩個對象,一個是“b”對象存在于字符串常量池中劝术,一個是 “bb”對象缩多,存在于堆中,但是此時字符串常量池中是沒有“bb”對象的养晋。 s3指向的是堆中的“bb”對象衬吆。

String s4 = s3.intern(); 調用了intern方法之后,在JDK1.6中绳泉,由于字符串常量池中沒有“bb”對象逊抡,故創(chuàng)建一個“bb”對象,然后返回其引用零酪。所以 s4 這個引用指向的是字符串常量池中新創(chuàng)建的“bb”對象秦忿。在JDK1.7中麦射,則把堆中“bb”對象的引用添加到字符串常量池中,故s4和s3所指向的對象是同一個灯谣,都指向堆中的“bb”對象潜秋。

String s5 = "bb"; 在JDK1.6中,指向字符串常量池中的“bb”對象的引用胎许,在JDK1.7中指向的是堆中“bb”對象的引用峻呛。

System.out.println(s3 == s5 ); 參照以上分析即可知道,在JDK1.6中③返回false(因為s3指向的是堆中的“bb”對象辜窑,s5指向的是字符串常量池中的“bb”對象)钩述,在JDK1.7中,③返回true(因為s3和s5指向的都是堆中的“bb”對象)穆碎。

System.out.println(s4 == s5); 在JDK1.6中牙勘,s4和s5指向的都是字符串常量池中創(chuàng)建的“bb”對象,在JDK1.7中所禀,s4和s5指向的都是堆中的“bb”對象方面。故無論JDK版本如何,④都返回true色徘。

綜上恭金,在JDK1.6中,返回的結果為:

false
true
false
true

在JDK1.7中褂策,返回結果為:

false
true
true
true

以上横腿,可以在JDK1.7和JDK1.6中分別驗證。注意一下斤寂,最好搞兩個項目然后分別設置不同的JDK耿焊,因為如果在一個項目中直接更改JDK版本,有可能高版本編譯之后遍搞,低版本編譯不通過罗侯。

原理搞懂了,我們再思考一下以下代碼的結果:

public class InternTest {
    public static void main(String[] args) {
        String str1 = "xy";
        String str2 = "z";
        String str3 = "xyz";
        String str4 = str1 + str2;
        String str5 = str4.intern();
        String str6 = "xy" + "z";

        System.out.println(str3 == str4); //⑤
        System.out.println(str3 == str5); //⑥
        System.out.println(str3 == str6); //⑦
    }
}

我們分析一下尾抑。

str1歇父、str2和str3都是簡單的定義字符串蒂培,所有它們都是在字符串常量池中創(chuàng)建對象再愈,然后引用指向字符串常量池中的對象。

String str4 = str1 + str2; 這段代碼和之前的 String s3 = new String("b") + new String("b"); 原理相同护戳,因此在堆中創(chuàng)建了一個“xyz”對象翎冲,然后str4指向堆中的這個對象。故⑤處返回false媳荒。(str3指向的是字符串常量池中的“xyz”對象)

String str5 = str4.intern(); 由于字符串常量池中已經存在“xyz”對象抗悍,因此不論是JDK1.6還是JDK1.7驹饺,此處返回的都是字符串常量池中對象的引用。所以str5指向字符串常量池中的對象缴渊,故 ⑥返回true赏壹。

String str6 = "xy" + "z"; 這段代碼需要說明一下,它不同于兩個字符串的引用拼接(如str1 + str2)衔沼。JVM會對其優(yōu)化處理蝌借,也就是在編譯階段會把“xy”和“z”進行拼接成為“xyz”,存放在字符串常量池指蚁。因此菩佑,str6指向的是字符串常量池的對象,故⑦返回true凝化。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末稍坯,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子搓劫,更是在濱河造成了極大的恐慌瞧哟,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糟把,死亡現場離奇詭異绢涡,居然都是意外死亡,警方通過查閱死者的電腦和手機遣疯,發(fā)現死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門雄可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缠犀,你說我怎么就攤上這事数苫。” “怎么了辨液?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵虐急,是天一觀的道長。 經常有香客問我滔迈,道長止吁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任燎悍,我火速辦了婚禮敬惦,結果婚禮上,老公的妹妹穿的比我還像新娘谈山。我一直安慰自己俄删,他們只是感情好,可當我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著畴椰,像睡著了一般臊诊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斜脂,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天抓艳,我揣著相機與錄音,去河邊找鬼帚戳。 笑死壶硅,一個胖子當著我的面吹牛,可吹牛的內容都是我干的销斟。 我是一名探鬼主播庐椒,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蚂踊!你這毒婦竟也來了约谈?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤犁钟,失蹤者是張志新(化名)和其女友劉穎棱诱,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體涝动,經...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡迈勋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了醋粟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靡菇。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖米愿,靈堂內的尸體忽然破棺而出厦凤,到底是詐尸還是另有隱情,我是刑警寧澤育苟,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布较鼓,位于F島的核電站,受9級特大地震影響违柏,放射性物質發(fā)生泄漏博烂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一漱竖、第九天 我趴在偏房一處隱蔽的房頂上張望禽篱。 院中可真熱鬧,春花似錦闲孤、人聲如沸谆级。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肥照。三九已至,卻和暖如春勤众,著一層夾襖步出監(jiān)牢的瞬間舆绎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工们颜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吕朵,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓窥突,卻偏偏與公主長得像努溃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子阻问,可洞房花燭夜當晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內容