String掃盲貼

字符串操作是最常見的操作犹褒。在Java中抵窒,往往使用String類來進行各種字符串操作。
而對于String這個類化漆,其實隱含不少特性估脆。對此,自己最近梳理了一遍座云。

字符串創(chuàng)建

常用方式主要兩種:

String a = "123"

String b = new String("123");

第一種方式疙赠,”123“直接存儲在常量池;第二種方式實際創(chuàng)建了兩個對象朦拖,第一個對象是”123“字符串在常量池中圃阳,第二個對象是在java堆中的String對象。

不可變

翻看jdk源碼璧帝,java.lang.String的定義是這樣的:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */  
    private int hash; // Default to 0  
    
    ...
    ...

以上代碼在jdk7捍岳,8都是一致的。

String類用final修飾睬隶,從java的final關(guān)鍵字語義上看锣夹,說明String不可繼承。

其次苏潜,String的底層其實用了一個final類型的char數(shù)組來存儲银萍,說明該字段一旦創(chuàng)建后就不能改變。

其中有個很容易引起迷惑的地方恤左,必須要弄清楚:String對象本質(zhì)是引用贴唇,我們所說的不可變是指引用指向的對象內(nèi)容不可變,并不是引用不可變飞袋。而String類中涉及修改的方法(substring戳气、replace、toLowerCase等)都是創(chuàng)建一個新的字符串巧鸭,并把這個它重新賦給引用瓶您。這就說明引用是重新指向了一個新的的字符串,但原來的字符串依舊存在內(nèi)存里。

雖然說String不可變览闰,但也不是絕對不可變芯肤,可以通過反射機制進行修改。然而大多情況不需要也沒必要用到反射压鉴,這里就不詳細討論了

具體可參考Java中的String為什么是不可變的? -- String源碼分析
里面闡述比較詳細了锻拘。

字符串‘+’操作

以前的文章中很多都說字符串‘+’操作會導(dǎo)致性能低效油吭,要用StringBuilder或StringBuffer。但其實現(xiàn)在的JVM已經(jīng)優(yōu)化得足夠強大署拟,

例如以下代碼

public class StringTest{
    public static void main(String[] args) {
        String a = "hello";
        String b = "abc" + "def" + 47 + a;
    }

通過 javap -c 反編譯出來的字節(jié)碼如下:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String hello
       2: astore_1
       3: new           #3                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #5                  // String abcdef47
      12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: aload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: return
}

我們可以看到婉宰,字符串的‘+’操作其實在實際執(zhí)行過程中,就是一個StringBuilder的append操作推穷。

再看這段代碼:

public class StringTest{
    public static void main(String[] args) {
        String a = "";
        for (int i=0;i<10;i++) {
            a = a + i;
        }
    }
}

對應(yīng)的字節(jié)碼:

public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: bipush        10
       8: if_icmpge     36
      11: new           #3                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
      18: aload_1
      19: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: iload_2
      23: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      26: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      29: astore_1
      30: iinc          2, 1
      33: goto          5
      36: return
}

對于循環(huán)內(nèi)的字符串拼接心包,雖然還是轉(zhuǎn)化為StringBuilder,但是情況有點不同馒铃,留意8:和 33:之間的代碼就是每一次循環(huán)所執(zhí)行的操作蟹腾,可以看到每一次的循環(huán)都創(chuàng)建了一個新的StringBuilder對象。

所以總結(jié)來說区宇,對于普通的一次性的‘+’操作娃殖,可以放心使用;但循環(huán)下的‘+’议谷,因為每一次都要new 一個StringBuilder而導(dǎo)致性能降低炉爆,因此還是先自己定義一個StringBuilder,然后每次循環(huán)通過append操作來完成

字符串常量池

JVM為了提高性能和減少內(nèi)存開銷卧晓,在實例化字符串常量的時候進行了一些優(yōu)化芬首。為了減少在JVM中創(chuàng)建的字符串的數(shù)量,字符串類維護了一個字符串池逼裆,每當創(chuàng)建字符串常量時郁稍,JVM會首先檢查字符串常量池。如果字符串已經(jīng)存在池中波附,就返回池中的實例引用艺晴。如果字符串不在池中,就會實例化一個字符串并放到池中

在JDK6中掸屡,字符串常量池在永久代分配內(nèi)存封寞;而JDK7開始,常量池已經(jīng)在Java堆上分配內(nèi)存仅财。

而字符串常量池本質(zhì)是個固定容量的HashMap狈究。 Java7和8可以通過 -XX:StringTableSize 設(shè)置其map size。
在Java6到Java7u40之前-XX:StringTableSize的默認大小是1009盏求;7u40之后擴大到60013抖锥。

-XX:+PrintStringTableStatistics 會在程序終止時打印字符串常量池的使用情況

String.intern

String.intern是把雙刃劍亿眠,用時需謹慎,切記切記0醴稀D上瘛!

String.intern()是一個Native方法拯勉,底層調(diào)用C++的 StringTable::intern 方法竟趾。

源碼注釋:當調(diào)用 intern 方法時,如果常量池中已經(jīng)存在該字符串宫峦,則返回池中的字符串岔帽;否則將此字符串添加到常量池中,并返回字符串的引用导绷。
在這里jdk6和jdk7表現(xiàn)有點區(qū)別:

  1. jdk6會直接生成一個新的字符串對象到常量池中犀勒,并返回該對象引用
  2. jdk7因為常量池不在Perm區(qū),不需要重新生成對象妥曲,而是直接存儲堆中的引用

關(guān)于stirng.intern的更深入分析可看
深入解析String#intern
以及
詳細可看白衣大神的String.intern() 祛魅

substring()

jdk6與jdk7中的實現(xiàn)方式不一樣贾费。

jdk6調(diào)用substring()雖然會創(chuàng)建一個新的字符串對象,但里面的char[] 仍然指向原來的那個,
因此對一個很長很長的字符串進行截取后,可能導(dǎo)致內(nèi)存泄露枕赵。

jdk7中substring()方法在堆中真正的創(chuàng)建了一個新的數(shù)組,原字符數(shù)組沒有被引用后就被GC回收了.因此避免了上述問題.

如有紕漏,敬請指出~

參考:
String.intern in Java 6, 7 and 8 – string pooling
JDK6和JDK7中的substring()方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箱玷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陌宿,更是在濱河造成了極大的恐慌锡足,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳坪,死亡現(xiàn)場離奇詭異舶得,居然都是意外死亡,警方通過查閱死者的電腦和手機爽蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門沐批,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝎亚,你說我怎么就攤上這事九孩。” “怎么了发框?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵躺彬,是天一觀的道長。 經(jīng)常有香客問我,道長宪拥,這世上最難降的妖魔是什么仿野? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮她君,結(jié)果婚禮上脚作,老公的妹妹穿的比我還像新娘。我一直安慰自己缔刹,他們只是感情好鳖枕,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桨螺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酿秸。 梳的紋絲不亂的頭發(fā)上灭翔,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音辣苏,去河邊找鬼肝箱。 笑死,一個胖子當著我的面吹牛稀蟋,可吹牛的內(nèi)容都是我干的煌张。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼退客,長吁一口氣:“原來是場噩夢啊……” “哼骏融!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起萌狂,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤档玻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后茫藏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體误趴,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年务傲,在試婚紗的時候發(fā)現(xiàn)自己被綠了凉当。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡售葡,死狀恐怖看杭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情天通,我是刑警寧澤泊窘,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響烘豹,放射性物質(zhì)發(fā)生泄漏瓜贾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一携悯、第九天 我趴在偏房一處隱蔽的房頂上張望祭芦。 院中可真熱鬧,春花似錦憔鬼、人聲如沸龟劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昌跌。三九已至,卻和暖如春照雁,著一層夾襖步出監(jiān)牢的瞬間蚕愤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工饺蚊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萍诱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓污呼,卻偏偏與公主長得像裕坊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子燕酷,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

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

  • ??需要說明的一點是籍凝,這篇文章是以《深入理解Java虛擬機》第二版這本書為基礎(chǔ)的,這里假設(shè)大家已經(jīng)了解了JVM的運...
    Geeks_Liu閱讀 14,015評論 5 44
  • 緣起 開始介紹 intern()方法前悟狱,先看一個簡單的 Java程序吧静浴!下面是一段 Java代碼,代碼內(nèi)容比較簡單...
    LilacZiyun閱讀 2,733評論 6 17
  • #海底兩萬里#【伙伴共讀第155天】 今晚讀《教學(xué)勇氣――漫步教師心靈》第四章挤渐。 優(yōu)質(zhì)教育總是重視...
    維C多閱讀 1,173評論 0 0
  • 先看效果 做起來很簡單苹享。就是用三角函數(shù),算出五邊形五個定點的坐標浴麻,然后根據(jù)左邊連線就行了得问。根據(jù)看圖。 具體代碼如下...
    shada閱讀 2,705評論 0 1
  • 聽見墻角的花開 低微呢喃 綠苔爬過荒階 破土開疆 陽光刺破落木 小蟻匆忙 蝶蟲互語 嗅一陣清風(fēng)松香 四野張望 好一...
    離離青青草閱讀 207評論 1 2