String是Java中最常用的類,是不可變的(Immutable), 那麼String是如何實作Immutable呢,String為什麼要設計成不可變呢?
關于String,收集一波基礎,來源标明最後,不确定是否權威, 希望有問題可以得到糾正。
Java8以及以後的字元串建立時,直接在堆中生成對象,而字元創常量池位于Metaspace。必要的時候,會把堆中的指針存入Metaspace, 而不是複制。
Metaspace位于虛拟機以外的直接記憶體,是以大小和外部直接記憶體有關,但也可以通過指定參數設定<code>-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m</code>
<a></a>
很難直接從百度出的中文資料中得到确切的答案,因為大多以訛傳訛,未經驗證。這裡且做測試,先記住,因為很不情願啃官方文檔。
首先,要有字元串常量池的概念。然後知道String是怎麼和常量池打交道的。這裡的武器就是<code>intern()</code>,看一下javadoc:
即常量池存在,傳回常量池中的那個對象,常量池不存在,則放入常量池,并傳回本身。由此推斷兩個公式:
通過判斷上述兩個公式,我們可以知道對象究竟是建立的,還是來自常量池,如此就可以坦然面對誰等于誰的問題。
為了準确表達,這裡為僞位址表示指針位置,比如<code>0xab</code>表示"ab"這個對象的位址
測試基于jdk1.8.0_131.jdk
作業系統: MacOS 10.12.6
記憶體: 16G
CPU: 2.2 GHz Intel Core i7
JDK提供一個可視化記憶體檢視工具<code>jvisualvm</code>。Mac由于安裝Java後已經設定了環境變量,是以打開指令行,直接輸入<code>jvisualvm</code>, 即可打開。Windows下應該是在bin目錄下找到對應的exe檔案,輕按兩下打開。
查詢一個内容為<code>RyanMiao</code>的字元串:
查詢字首為<code>Ryan</code>的字元串:
周遊
沒找到比對字首的做法,這裡使用最笨的周遊
通過<code>=</code>号建立對象,運作時隻有一個對象存在。
通過Java自帶的工具Java VisualVM來查詢記憶體中的String執行個體,可以看出s1隻有一個對象。操作方法如下。
為了動态檢視記憶體,選擇休眠1h,run <code>testNewStr()</code>,然後打開jvisualvm, 可以看到幾個vm清單,找到我們的vm,右鍵heamp dump.
然後,選擇右側的OQL,在查詢内容編輯框裡輸入:
可以發現,隻有一個對象。
通過new建立對象時,參數<code>RyanMiao</code>作為字面量會生成一個對象,并存入字元創常量池。而後,new的時候又将建立另一個String對象,是以,最好不要采用這種方式使用String, 不然就是雙倍消耗記憶體。
當字元創常量池不存在此對象的的時候,傳回本身。
在Java Visual VM中,查詢以"Ryan"開頭的變量:
但,根據以上幾個例子,可以明顯看出來,字元串字面量(literal)都是對象,于是上栗中應該有三個對象:<code>Ryan</code>,<code>Miao</code>,<code>RyanMiao</code>。驗證如下:
此時的記憶體模型:
驗證如下:
使用指令行工具<code>javap -c TestString</code>可以反編譯class,看到指令執行的過程。
我以為使用了StringBuilder可以減少性能損耗啊,然而,編譯後的檔案直接說no,直接給替換成拼接了:
Immutable是指String的對象執行個體生成後就不可以改變。相反,加入一個user類,你可以修改name,那麼就不叫做Immutable。是以,String的内部屬性必須是不可修改的。
String的内部很簡單,有兩個私有成員變量:
而後并沒有對外提供可以修改這兩個屬性的方法,沒有set,沒有build。
String有很多public方法,要想維護這麼多方法下的不可變需要付出代價。每次都将建立新的String對象。比如,這裡講一個很有迷惑性的concat方法:
從方法名上看,是拼接字元串。這樣下意識以為是原對象修改了内容,是以對于<code>str2 = str.concat("abc")</code>,會認為是<code>str2==str</code>。然後熟記String不可變定律的你肯定會反對。确實不是原對象,确實<code>new</code>了新String。同樣的道理,在其他String的public方法裡,都将new一個新的String。是以就保證了原對象的不可變。說到這裡,下面的結果是什麼?
按照String不可變的特性來了解,這裡str2應該是生成的新對象,那麼肯定不等于str.是以是對的,是false。面試考這種題目也是醉了,為了考驗大家對String API的熟悉程度嗎?看源碼才知道,當拼接的内容為空的時候直接傳回原對象。是以,str2==str是true。
由于String被聲明式final的,則我們不可以繼承String,是以就不能通過繼承來複寫一些關于hashcode和value的方法。
一下内容來自http://www.kogonuso.com/2015/03/why-string-is-immutable-or-final-class.html#sthash.VgLU1mDY.dpuf. 發現百度的中文版本基本也是此文的翻譯版。
String是不可變的。因為String會被String pool緩存。因為緩存String字面量要在多個線程之間共享,一個用戶端的行為會影響其他所有的用戶端,是以會産生風險。如果其中一個用戶端修改了内容"Test"為“TEST”, 其他用戶端也會得到這個結果,但顯然并想要這個結果。因為緩存字元串對性能來說至關重要,是以為了移除這種風險,String被設計成Immutable。
HashMap在Java裡太重要了,而它的key通常是String類型的。如果String是mutable,那麼修改屬性後,其hashcode也将改變。這樣導緻在HashMap中找不到原來的value。
string的subString方法如下:
如果String是可變的,即修改String的内容後,位址不變。那麼當多個線程同時修改的時候,value的length是不确定的,造成不安全因素,無法得到正确的截取結果。而為了保證順序正确,需要加<code>synchronzied</code>,但這會得到難以想象的性能問題。
這和上條中HashMap的需要一樣,不可變的好處就是hashcode不會變,可以緩存而不用計算。
The absolutely most important reason that String is immutable is that it is used by the class loading mechanism, and thus have profound and fundamental security aspects. Had String been mutable, a request to load "java.io.Writer" could have been changed to load "mil.vogoon.DiskErasingWriter"
String會在加載class的時候需要,如果String可變,那麼可能會修改加載中的類。
總之,安全性和String字元串常量池緩存是String被設計成不可變的主要原因。
本文轉自Ryan.Miao部落格園部落格,原文連結:http://www.cnblogs.com/woshimrf/p/why-string-is-immutable.html,如需轉載請自行聯系原作者