系統(tǒng)優(yōu)化專題2——String

字符串性能優(yōu)化

String對象是我們使用最頻繁的一個對象類型磕道,但它的性能問題卻是最容易被忽略的。String對象作為Java語言中重要的數(shù)據(jù)類型讳侨,可以說是在內(nèi)存中占據(jù)空間最大的一個對象呵萨。高效地使用字符串,可以提升系統(tǒng)的整體性能跨跨。

我們從String對象的實(shí)現(xiàn)潮峦、特性以及實(shí)際使用中的優(yōu)化這三個方面入手,深入了解勇婴。

先看如下代碼忱嘹,創(chuàng)建3個對象,依次兩兩匹配耕渴,每組的結(jié)果是否相等拘悦?

String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
asserSame(str1 == str2);
asserSame(str2 == str3);
asserSame(str1 == str3);

String對象是如何實(shí)現(xiàn)的?

在Java語言中萨螺,對String對象做了大量優(yōu)化窄做,來節(jié)約內(nèi)存空間愧驱,提升String對象在系統(tǒng)中的性能慰技,優(yōu)化過程如圖所示:


image.png
  • 在Java6及之前版本中,String對象是對char數(shù)組進(jìn)行封裝實(shí)現(xiàn)的對象组砚,主要有四個成員變量:char數(shù)組吻商、偏移量offset、字符數(shù)量count糟红、哈希值hash艾帐。String對象通過offset和count 兩個屬性來定位char[]數(shù)組,獲取字符串盆偿。這樣做可以高效柒爸、快速地共享數(shù)組對象,同時(shí)節(jié)省內(nèi)存空間事扭,但這種方式很有可能會導(dǎo)致內(nèi)存泄漏捎稚。
  • Java7版本和Java8版本,Java對String類做了一些改變求橄。String類中不再有offset和count兩個變量了今野。這樣做的好處是String對象占用的內(nèi)存減少了,同時(shí)String.substring()方法不再共享原對象的char[]罐农,從而解決了使用該方法可能導(dǎo)致的內(nèi)存泄露問題条霜。
  • 從Java9開始,Java將char[]改為了byte[]字段涵亏,維護(hù)了新的屬性coder宰睡,它是一個編碼格式的標(biāo)識蒲凶。我們知道一個char字符占16位,2個字節(jié)夹厌。這個情況下豹爹,存儲單字節(jié)編碼內(nèi)的字符就顯得非常浪費(fèi)。JDK9的String類為了節(jié)約內(nèi)存空間矛纹,使用了占8位臂聋,一個字節(jié)的byte數(shù)組來存放字符串。新屬性coder的作用是或南,在計(jì)算字符串長度或者使用indexOf()函數(shù)時(shí)孩等,需要根據(jù)這個字段,判斷如何計(jì)算字符串長度采够。coder屬性默認(rèn)0或1肄方,0代表Latin-1單字節(jié),1代表UTF-16蹬癌。如果String判斷字符串只含有Latin-1則coder等于0否則等于1权她。

String對象的不可變性

觀察源碼可以知道,String類是被final關(guān)鍵字修飾的逝薪,并且變量char[]也被final修飾了隅要。我們知道被final修飾的類不可繼承,變量被final+private修飾就不可更改董济。Java實(shí)現(xiàn)的這個特性叫做String對象的不可變性步清,即String對象一旦創(chuàng)建成功,就不能修改虏肾。這樣做有什么好處呢廓啊?

  1. 保證了String對象的安全性。保證不會被惡意修改封豪。
  2. 保證hash屬性值不會頻繁變更谴轮,使得類似HashMap容器能實(shí)現(xiàn)key-value緩存。
  3. 能夠?qū)崿F(xiàn)字符串常量池吹埠,當(dāng)代碼使用String str = "abc"; 創(chuàng)建對象時(shí)第步,JVM首先會檢查該對象是否在字符串常量池中,如果在藻雌,則返回其引用雌续,否則在常量池中被創(chuàng)建,這種實(shí)現(xiàn)可以減少相同對象的重復(fù)創(chuàng)建胯杭,節(jié)約內(nèi)存驯杜。當(dāng)使用String str = new String("abc");創(chuàng)建對象時(shí),首先在編譯類文件時(shí)做个,會將"abc"常量放到常量結(jié)構(gòu)中鸽心,在類加載時(shí)滚局,"abc"會在常量池中創(chuàng)建;其次顽频,在調(diào)用new時(shí)藤肢,JVM將會調(diào)用String的構(gòu)造函數(shù),同時(shí)引用常量池的"abc"糯景,在堆中創(chuàng)建一個String對象嘁圈;最后str會引用String對象。
    而我們平時(shí)的使用中會發(fā)現(xiàn)蟀淮,String str="abc";str="bcd";這樣的語句最住,這里str是可變的。其實(shí)怠惶,這里的str只是對String對象的引用涨缚,原來的對象仍舊存在于內(nèi)存中。

String對象的優(yōu)化

接下來我們根據(jù)String對象的特性策治,看看如何優(yōu)化String對象脓魏,優(yōu)化的過程中有什么需要注意的地方。

  1. 構(gòu)建超大字符串

    String str = "a"+"b"+"c";
    

    對于上面的代碼通惫,我們知道茂翔,JVM首先會生成a、b讽膏、c三個對象檩电,最后生成abc對象拄丰,理論上來講這樣的代碼效率會很低府树。但在實(shí)際運(yùn)行中,我們就會發(fā)現(xiàn)料按,編譯器自動將這條語句優(yōu)化為

    String str = "abc";
    

    上述代碼是字符串常量的累加奄侠,那么對于字符串變量,編譯器是否會進(jìn)行同樣的優(yōu)化呢载矿?
    對于

    String str="abc";
    for(int i=0;i<100;i++){
         str+=i;
    }
    

    這段代碼垄潮,編譯器同樣會進(jìn)行優(yōu)化税弃,優(yōu)化的結(jié)果是這樣的

    String str="abc";
    for(int i=0;i<100;i++){
         str=(new StringBuilder(String.valueOf(str))).append(i).toString();
    }
    

    綜上怕磨,即使使用+進(jìn)行字符串拼接,也同樣會被編譯器優(yōu)化為StringBuilder方式赁严,但我們發(fā)現(xiàn)逢勾,編譯器的優(yōu)化牡整,每次循環(huán)就會創(chuàng)建一個新的StringBuilder對象,同樣會降低系統(tǒng)性能溺拱。所以逃贝,平時(shí)進(jìn)行字符串拼接的時(shí)候谣辞,建議顯式使用StringBuilder提升系統(tǒng)性能。在多線程編程中String對象的拼接涉及到線程安全沐扳,我們可以使用StringBuffer泥从,但是StringBuffer涉及到鎖競爭,所以從性能上來說沪摄,要比StringBuilder差一些躯嫉。

  2. 使用String.intern節(jié)省內(nèi)存,每次賦值時(shí)使用String的intern方法杨拐,可以大幅度降低重復(fù)信息的內(nèi)存占用率和敬。調(diào)用intern方法,JVM回去檢查字符串常量池中是否有等于該對象的字符串的引用戏阅,如果沒有昼弟,在JDK1.6中會復(fù)制堆內(nèi)存中的字符串到常量池中,并返回引用奕筐,堆內(nèi)存中的字符串會通過垃圾回收器回收舱痘。在JDK1.7后,常量池合并到堆中离赫,不需要再復(fù)制字符串芭逝,只會把首次遇到的字符串的引用添加到常量池中;如果有渊胸,就返回引用旬盯。


    image.png

    使用intern方法需要注意,一定要結(jié)合場景翎猛。常量池是類似HashTable的實(shí)現(xiàn)胖翰,存儲的數(shù)據(jù)越大,遍歷的時(shí)間復(fù)雜度越大切厘,數(shù)據(jù)如果過大萨咳,會增大字符串常量池的負(fù)擔(dān)。

  3. 謹(jǐn)慎選擇字符串分割疫稿,Split()作為分割字符串的方法培他,其內(nèi)部是使用正則表達(dá)式來實(shí)現(xiàn)的,會出現(xiàn)回溯的風(fēng)險(xiǎn)遗座,建議使用indexOf方法代替Split方法完成分割舀凛。如果一定要使用Split方法,就需要對回溯問題加以重視途蒋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猛遍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌螃壤,老刑警劉巖抗果,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奸晴,居然都是意外死亡冤馏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進(jìn)店門寄啼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逮光,“玉大人,你說我怎么就攤上這事墩划√楦眨” “怎么了?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵乙帮,是天一觀的道長杜漠。 經(jīng)常有香客問我,道長察净,這世上最難降的妖魔是什么驾茴? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮氢卡,結(jié)果婚禮上锈至,老公的妹妹穿的比我還像新娘。我一直安慰自己译秦,他們只是感情好峡捡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筑悴,像睡著了一般们拙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雷猪,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天睛竣,我揣著相機(jī)與錄音晰房,去河邊找鬼求摇。 笑死,一個胖子當(dāng)著我的面吹牛殊者,可吹牛的內(nèi)容都是我干的与境。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼猖吴,長吁一口氣:“原來是場噩夢啊……” “哼摔刁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起海蔽,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤共屈,失蹤者是張志新(化名)和其女友劉穎绑谣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拗引,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡借宵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矾削。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壤玫。...
    茶點(diǎn)故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖哼凯,靈堂內(nèi)的尸體忽然破棺而出欲间,到底是詐尸還是另有隱情,我是刑警寧澤断部,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布猎贴,位于F島的核電站,受9級特大地震影響蝴光,放射性物質(zhì)發(fā)生泄漏嘱能。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一虱疏、第九天 我趴在偏房一處隱蔽的房頂上張望惹骂。 院中可真熱鬧,春花似錦做瞪、人聲如沸对粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽著拭。三九已至,卻和暖如春牍帚,著一層夾襖步出監(jiān)牢的瞬間儡遮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工暗赶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鄙币,地道東北人。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓蹂随,卻偏偏與公主長得像十嘿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子岳锁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評論 2 355