1. 程式人生 > >Java字串池(String Pool)深度解析

Java字串池(String Pool)深度解析

在工作中,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出來的字串物件存放在堆中。

原文http://www.cnblogs.com/fangfuhai/p/5500065.html