1. 程式人生 > >理解Java String和String Pool

理解Java String和String Pool

要理解 java中String的運作方式,必須明確一點:String是一個非可變類(immutable)。什麼是非可變類呢?簡單說來,非可變類的例項是不能被修改的,每個例項中包含的資訊都必須在該例項建立的時候就提供出來,並且在物件的整個生存週期內固定不變。java為什麼要把String設計為非可變類呢?你可以問問 james Gosling :)。但是非可變類確實有著自身的優勢,如狀態單一,物件簡單,便於維護。其次,該類物件物件本質上是執行緒安全的,不要求同步。此外使用者可以共享非可變物件,甚至可以共享它們的內部資訊。(詳見 《Effective java》item 13)。String類在java中被大量運用,甚至在class檔案中都有其身影,因此將其設計為簡單輕便的非可變類是比較合適的。 
一、建立。
    好了,知道String是非可變類以後,我們可以進一步瞭解String的構造方式了。建立一個Stirng物件,主要就有以下兩種方式:

java 程式碼 
    String str1 = new String("abc");     
    Stirng str2 = "abc";  

    雖然兩個語句都是返回一個String物件的引用,但是jvm對兩者的處理方式是不一樣的。對於第一種,jvm會在內部維護的strings pool中放入一個"abc"物件,並在heap中建立一個String物件,然後將該heap中物件的引用返回給使用者。對於第二種,jvm首先會在內部維護的strings pool中通過String的equels方法查詢是物件池中是否存放有該String物件,如果有,則返回已有的String物件給使用者,而不會在heap中重新建立一個新的String物件;如果物件池中沒有該String物件,jvm則建立新的String物件新增至strings pool中,將其引用返回給使用者。注意:使用第一種方法建立物件時,jvm是會主動把該物件放到strings pool裡面的。看下面的例子:

java 程式碼 
    String str1 = new String("abc"); //jvm 在堆上建立一個String物件並在內部維護的strings pool中放入一個"abc"物件   
   
    //jvm在strings pool中能找到值為“abc”的字串,將其引用直接返回給str2
    Stirng str2 = "abc";    
   
    if(str1 == str2){
        System.out.println("str1 == str2");    
    }else{    
        System.out.println("str1 != str2");    
    }    
    //列印結果是 str1 != str2,因為前者指向heap中的物件,後者指向strings pool中的物件    
   
    String str3 = "abc";    
    //此時,jvm發現strings pool中已有“abc”物件了,因為“abc”equels “abc”    
    //因此直接返回str2指向的物件給str3,也就是說str2和str3是指向同一個物件的引用    
    if(str2 == str3){    
        System.out.println("str2 == str3");    
    }else{    
        System.out.println("str2 != str3");    
    }    
    //列印結果為 str2 == str3  

    再看下面的例子:

java 程式碼 
    String str1 = new String("abc"); //jvm 在堆上建立一個String物件並在內部維護的strings pool中放入一個"abc"物件 
   
    str1 = str1.intern();

    //程式顯式將str1引用由原來的指向heap中物件改為指向內部維護的strings pool中的物件。
    /*intern的作用是:Returns a canonical representation for the string object. intern執行過程是這樣的:When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned. */
    //執行完該語句後,str1原來指向的String物件已經成為垃圾物件了,隨時會    
    //被GC收集。    
   
    //此時,str1指向strings pool中的"abc”物件,也就是說str2和str1引用著同一個物件。  
    Stirng str2 = "abc";    
   
    if(str1 == str2){    
        System.out.println("str1 == str2");    
    }else{    
        System.out.println("str1 != str2");    
    }    
   //列印結果是 str1 == str2   

   為什麼jvm可以這樣處理String物件呢?就是因為String的非可變性。既然所引用的物件一旦建立就永不更改,那麼多個引用共用一個物件時互不影響。

   但是如果用+號來實現String的串接時:1)僅當+號兩邊均為字串常量時,才將其+後的結果當做字串常量,且該結果直接放入strings pool;2)若+號兩邊有一方為變數時,+後的結果即當做非字串常量處理(等同於new String()的效果)。

二、串接(Concatenation)。
   java程式設計師應該都知道濫用String的串接操作符是會影響程式的效能的。效能問題從何而來呢?歸根結底就是String類的非可變性。既然String物件都是非可變的,也就是物件一旦建立了就不能夠改變其內在狀態了,但是串接操作明顯是要增長字串的,也就是要改變String的內部狀態,兩者出現了矛盾。怎麼辦呢?要維護String的非可變性,只好在串接完成後新建一個String 物件來表示新產生的字串了。也就是說,每一次執行串接操作都會導致新物件的產生,如果串接操作執行很頻繁,就會導致大量物件的建立,效能問題也就隨之而來了。
   為了解決這個問題,jdk為String類提供了一個可變的配套類,StringBuffer。使用StringBuffer物件,由於該類是可變的,串接時僅僅時改變了內部資料結構,而不會建立新的物件,因此效能上有很大的提高。針對單執行緒,jdk 5.0還提供了StringBuilder類,在單執行緒環境下,由於不用考慮同步問題,使用該類使效能得到進一步的提高。

三、String的長度
   我們可以使用串接操作符得到一個長度更長的字串,那麼,String物件最多能容納多少字元呢?檢視String的原始碼我們可以得知類String中是使用域 count 來記錄物件字元的數量,而count 的型別為 int,因此,我們可以推測最長的長度為 2^32,也就是4G。
   不過,我們在編寫原始碼的時候,如果使用 Sting str = "aaaa";的形式定義一個字串,那麼雙引號裡面的ASCII字元最多隻能有 65534 個。為什麼呢?因為在class檔案的規範中, CONSTANT_Utf8_info表中使用一個16位的無符號整數來記錄字串的長度的,最多能表示 65536個位元組,而java class 檔案是使用一種變體UTF-8格式來存放字元的,null值使用兩個位元組來表示,因此只剩下 65536- 2 = 65534個位元組。也正是變體UTF-8的原因,如果字串中含有中文等非ASCII字元,那麼雙引號中字元的數量會更少(一箇中文字元佔用三個位元組)。如果超出這個數量,在編譯的時候編譯器會報錯。