標籤:記憶體佔用 添加 避免 這就是我 代碼 比較 尋找 頻率 span
出自 http://www.cnblogs.com/fangfuhai/p/5500065.html
在工作中,String類是我們使用頻率非常高的一種物件類型。JVM為了提升效能和減少記憶體開銷,避免字串的重複建立,其維護了一塊特殊的記憶體空間,這就是我們今天要討論的核心,即字串池(String Pool)。字串池由String類私人的維護。
我們知道,在Java中有兩種建立字串對象的方式:1)採用字面值的方式賦值 2)採用new關鍵字建立一個字串對象。這兩種方式在效能和記憶體佔用方面存在著差別。
方式一:採用字面值的方式賦值,例如:
採用字面值的方式建立一個字串時,JVM首先會去字串池中尋找是否存在"aaa"這個對象,如果不存在,則在字串池中建立"aaa"這個對象,然後將池中"aaa"這個對象的引用地址返回給字串常量str,這樣str會指向池中"aaa"這個字串對象;如果存在,則不建立任何對象,直接將池中"aaa"這個對象的地址返回,賦給字串常量。
在本例中,執行:str == str2 ,會得到以下結果:
這是因為,建立字串對象str2時,字串池中已經存在"aaa"這個對象,直接把對象"aaa"的引用地址返回給str2,這樣str2指向了池中"aaa"這個對象,也就是說str和str2指向了同一個對象,因此語句System.out.println(str == str2)輸出:true。
方式二:採用new關鍵字建立一個字串對象,例如:
採用new關鍵字建立一個字串對象時,JVM首先在字串池中尋找有沒有"aaa"這個字串對象,如果有,則不在池中再去建立"aaa"這個對象了,直接在堆中建立一個"aaa"字串對象,然後將堆中的這個"aaa"對象的地址返回賦給引用str3,這樣,str3就指向了堆中建立的這個"aaa"字串對象;如果沒有,則首先在字串池中建立一個"aaa"字串對象,然後再在堆中建立一個"aaa"字串對象,然後將堆中這個"aaa"字串對象的地址返回賦給str3引用,這樣,str3指向了堆中建立的這個"aaa"字串對象。
在這個例子中,執行:str3 == str4,得到以下結果:
因為,採用new關鍵字建立對象時,每次new出來的都是一個新的對象,也即是說引用str3和str4指向的是兩個不同的對象,因此語句System.out.println(str3 == str4)輸出:false。
字串池的實現有一個前提條件:String對象是不可變的。因為這樣可以保證多個引用可以同事指向字串池中的同一個對象。如果字串是可變的,那麼一個引用操作改變了對象的值,對其他引用會有影響,這樣顯然是不合理的。
字串池的優缺點:字串池的優點就是避免了相同內容的字串的建立,節省了記憶體,省去了建立相同字串的時間,同時提升了效能;另一方面,字串池的缺點就是犧牲了JVM在常量池中遍曆對象所需要的時間,不過其時間成本相比而言比較低。
intern方法使用:一個初始為空白的字串池,它由類String獨自維護。當調用 intern方法時,如果池已經包含一個等於此String對象的字串(用equals(oject)方法確定),則返回池中的字串。否則,將此String對象添加到池中,並返回此String對象的引用。 對於任意兩個字串s和t,若且唯若s.equals(t)為true時,s.instan() == t.instan才為true。所有字面值字串和字串賦值常量運算式都使用 intern方法進行操作。
GC回收:字串池中維護了共用的字串對象,這些字串不會被垃圾收集器回收。
Java語言規範(Java Language Specification)中對字串做出了如下說明:每一個字串常量都是指向一個字串類執行個體的引用。字串對象有一個固定值。字串常量,或者一般的說,常量運算式中的字串都被使用方法 String.intern進行保留來共用唯一的執行個體。以上是Java語言規範中的原文,比較官方,用更通俗易懂的語言翻譯過來主要說明了三點:1)每一個字串常量都指向字串池中或者堆記憶體中的一個字串執行個體;2)字串對象值是固定的,一旦建立就不能再修改;3)字串常量或者常量運算式中的字串都被使用方法String.intern()在字串池中保留了唯一的執行個體。並且給出了測試程式如下:
編譯單元:
輸出:
這個例子說明了6點:
- 同一個包下同一個類中的字串常量的引用指向同一個字串對象;
- 同一個包下不同的類中的字串常量的引用指向同一個字串對象;
- 不同的包下不同的類中的字串常量的引用仍然指向同一個字串對象;
- 由常量運算式計算出的字串是在編譯時間進行計算,然後被當作常量;
- 在運行時通過串連計算出的字串是新建立的,因此是不同的;
- 通過計算產生的字串顯示調用intern方法後產生的結果與原來存在的同樣內容的字串常量是一樣的。
從上面的例子可以看出,字串常量在編譯時間計算和在運行時計算,其執行過程是不同的,得到的結果也是不同的。我們來看看下面這段代碼:
代碼輸出如下:
為什麼出現上面的結果呢?這是因為,字串字面量拼接操作是在Java編譯器編譯期間就執行了,也就是說編譯器編譯時間,直接把"java"、"language"和"specification"這三個字面量進行"+"操作得到一個"javalanguagespecification" 常量,並且直接將這個常量放入字串池中,這樣做實際上是一種最佳化,將3個字面量合成一個,避免了建立多餘的字串對象。而字串引用的"+"運算是在Java運行期間執行的,即str + str2 + str3在程式執行期間才會進行計算,它會在堆記憶體中重新建立一個拼接後的字串對象。總結來說就是:字面量"+"拼接是在編譯期間進行的,拼接後的字串存放在字串池中;而字串引用的"+"拼接運算實在運行時進行的,新建立的字串存放在堆中。
總結:字串是常量,字串池中的每個字串對象只有唯一的一份,可以被多個引用所指向,避免了重複建立內容相同的字串;通過字面值賦值建立的字串對象存放在字串池中,通過關鍵字new出來的字串對象存放在堆中。
Java字串池(String Pool)深度解析(轉)