深度解析串池

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  來自:風中程序猿

風中程序猿

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章