頭條面試官:String、StringBuffer、StringBuilder的區別(下)

哈嘍~ 又到了美麗的週五,而我仍然是忙碌工作的小姐姐。

這兩天看到最熱的新聞要數這一條:

6000萬牽手抖音,羅永浩開啟直播帶貨

頭條面試官:String、StringBuffer、StringBuilder的區別(下)

好久沒見老羅的身影,再一出現就變成了“帶貨達人”?


雖然還不知道這次他能不能成功,如今電商直播平臺那麼多,他卻獨挑抖音,相信還是看中其粉絲經濟,側面也說明頭條旗下產品的實力。


那今天我還是接著上期的頭條面試考點繼續延伸,老規矩,這次更新的實踐乾貨!


01


字符串設計和實現考量


頭條面試官:String、StringBuffer、StringBuilder的區別(下)

頭條面試官:String、StringBuffer、StringBuilder的區別(下)

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


其實,在通常情況下,沒有必要過於擔心,要相信Java還是非常智能的。


我們來做個實驗,把下面一段代碼,利用不同版本的JDK編譯,然後再反編譯,例如:

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


先編譯再反編譯,比如使用JDK 9:

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


JDK 8的輸出片段是:

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


而在JDK 9中,反編譯的結果就非常簡單了,片段是:

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


你可以看到,在JDK 8中,字符串拼接操作會自動被javac轉換為StringBuilder操作,而在JDK 9裡面則是因為Java 9為了更加統一字符串操作優化,提供了StringConcatFactory,作為一個統一的入口。javac自動生成的代碼,雖然未必是最優化的,但普通場景也足夠了

,你可以酌情選擇。


02


字符串緩存


我們粗略統計過,把常見應用進行堆轉儲(Dump Heap),然後分析對象組成,會發現平均25%的對象是字符串,並且其中約半數是重複的。如果能避免創建重複字符串,可以有效降低內存消耗和對象創建開銷。


String在Java 6以後提供了intern()方法,目的是提示JVM把相應字符串緩存起來,以備重複使用。在我們創建字符串對象並調用intern()方法的時候,如果已經有緩存的字符串,就會返回緩存裡的實例,否則將其緩存起來。一般來說,JVM會將所有的類似“abc”這樣的文本字符串,或者字符串常量之類緩存起來。


看起來很不錯是吧?但實際情況估計會讓你大跌眼鏡。


一般使用Java 6這種歷史版本,並不推薦大量使用intern,為什麼呢?魔鬼存在於細節中,被緩存的字符串是存在所謂PermGen裡的,也就是臭名昭著的“永久代”,這個空間是很有限的,也基本不會被FullGC之外的垃圾收集照顧到。所以,如果使用不當,OOM就會光顧。


在後續版本中,這個緩存被放置在堆中,這樣就極大避免了永久代佔滿的問題,甚至永久代在JDK 8中被MetaSpace(元數據區)替代了。而且,默認緩存大小也在不斷地擴大中, 從最初的1009,到7u40以後被修改為60013。


你可以使用下面的參數直接打印具體數字,可以拿自己的JDK立刻試驗一下。

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


你也可以使用下面的JVM參數手動調整大小,但是絕大部分情況下並不需要調整,除非你確定它的大小已經影響了操作效率。

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


Intern是一種顯式地排重機制,但是它也有一定的副作用,因為需要開發者寫代碼時明確調用,

一是不方便,每一個都顯式調用是非常麻煩的;另外就是我們很難保證效率,應用開發階段很難清楚地預計字符串的重複情況,有人認為這是一種汙染代碼的實踐。


幸好在Oracle JDK 8u20之後,推出了一個新的特性,也就是G1 GC下的字符串排重。它是通過將相同數據的字符串指向同一份數據來做到的,是JVM底層的改變,並不需要Java類庫做什麼修改。


注意這個功能目前是默認關閉的,你需要使用下面參數開啟,並且記得指定使用G1 GC:

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


前面說到的幾個方面,只是Java底層對字符串各種優化的一角,在運行時,字符串的一些基礎操作會直接利用JVM內部的Intrinsic機制,往往運行的就是特殊優化的本地代碼,而根本就不是Java代碼生成的字節碼。


Intrinsic可以簡單理解為,是一種利用native方式hard-coded的邏輯,算是一種特別的內聯,很多優化還是需要直接使用特定的CPU指令,具體可以看相關源碼,搜索“string”以查找相關Intrinsic定義。當然,你也可以在啟動實驗應用時,使用下面參數,瞭解intrinsic發生的狀態。

頭條面試官:String、StringBuffer、StringBuilder的區別(下)

可以看出,僅僅是字符串一個實現,就需要Java平臺工程師和科學家付出如此大且默默無聞的努力,我們得到的很多便利都是來源於此。


03


String自身的演化


頭條面試官:String、StringBuffer、StringBuilder的區別(下)

頭條面試官:String、StringBuffer、StringBuilder的區別(下)


04


小結


今天我從String、StringBuffer和StringBuilder的主要設計和實現特點開始,分析了字符串緩存的intern機制、非代碼侵入性的虛擬機層面排重、Java 9中緊湊字符的改進,並且初步接觸了JVM的底層優化機制intrinsic。


從實踐的角度,不管是Compact Strings還是底層intrinsic優化,都說明了使用Java基礎類庫的優勢,它們往往能夠得到最大程度、最高質量的優化,而且只要升級JDK版本,就能零成本地享受這些益處。


關於今天我們討論的題目你做到心中有數了嗎?限於篇幅有限,還有很多字符相關的問題沒有來得及討論,比如編碼相關的問題。以後有機會的話Emma會再跟大家探討。


碼了這麼多,最後依然是Emma的福利環節~


本期福利:頭條最新資料包,內含【最新面試題+筆試題+面經】


領取方式:

關注【愛碼士Emma】頭條號,回覆口令「頭條」即可領資料包

更多技術乾貨、真題和麵試經驗分享盡在【愛碼士Emma】

我們下期再見~


分享到:


相關文章: