1. 程式人生 > >深入瞭解Java字串常量池

深入瞭解Java字串常量池

java中有幾種不同的常量池,以下的內容是對java中幾種常量池的介紹以及重點研究一下字串常量池。

class常量池

我們寫的每一個Java類被編譯後,就會形成一份class檔案;class檔案中除了包含類的版本、欄位、方法、介面等描述資訊外,還有一項資訊就是常量池(constant pool table),用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References),每個class檔案都有一個class常量池。
其中字面量包括:1.文字字串 2.八種基本型別的值 3.被宣告為final的常量等;符號引用包括:1.類和方法的全限定名 2.欄位的名稱和描述符 3.方法的名稱和描述符。

執行時常量池

執行時常量池存在於記憶體中,也就是class常量池被載入到記憶體之後的版本,是方法區的一部分。不同之處是:它的字面量可以動態的新增(String類的intern()),符號引用可以被解析為直接引用。

JVM在執行某個類的時候,必須經過載入、連線、初始化,而連線又包括驗證、準備、解析三個階段。而當類載入到記憶體中後,jvm就會將class常量池中的內容存放到執行時常量池中,由此可知,執行時常量池也是每個類都有一個。在解析階段,會把符號引用替換為直接引用,解析的過程會去查詢字串常量池,也就是我們下面要說的StringTable,以保證執行時常量池所引用的字串與字串常量池中是一致的。

字串常量池

在JDK6.0及之前版本,字串常量池存放在方法區中在JDK7.0版本以後,字串常量池被移到了堆中了。至於為什麼移到堆內,大概是由於方法區的記憶體空間太小了。

在HotSpot VM裡實現的string pool功能的是一個StringTable類,它是一個Hash表,預設值大小長度是1009;這個StringTable在每個HotSpot VM的例項只有一份,被所有的類共享。字串常量由一個一個字元組成,放在了StringTable上。

在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash衝突,導致連結串列過長,當呼叫String#intern()時會需要到連結串列上一個一個找,從而導致效能大幅度下降;在JDK7.0中,StringTable的長度可以通過引數指定。

下面看一下例項:

String s = new String("abc")

這條語句建立了幾個物件?

答案:共2個。第一個物件是”abc”字串儲存在常量池中,第二個物件在JAVA Heap中的 String 物件。這裡不要混淆了s是放在棧裡面的指向了Heap堆中的String物件。

比較下列兩種建立字串的方法:

String str1 = new String("abc");
String str2 = "abc";

答案:第一種是用new()來新建物件的,它會在存放於堆中。每呼叫一次就會建立一個新的物件。執行時期建立
第二種是先在棧中建立一個對String類的物件引用變數str2,然後通過符號引用去字串常量池裡找有沒有”abc”,如果沒有,則將”abc”存放進字串常量池,並令str2指向”abc”,如果已經有”abc” 則直接令str2指向“abc”。“abc”存於常量池在編譯期間完成

String s1 = new String("s1") ;
String s1 = new String("s1") ;

上面一共建立了幾個物件?

答案:答案:3個 ,編譯期Constant Pool中建立1個,執行期heap中建立2個.(用new建立的每new一次就在堆上建立一個物件,用引號建立的如果在常量池中已有就直接指向,不用建立)

比較字串的‘==’和‘equals()’區別?

答案:

‘==’ 比較的是變數(棧)記憶體中存放的物件的(堆)記憶體地址,用來判斷兩個物件的地址是否相同,即是否是指相同一個物件。比較的是真正意義上的指標操作。注意:
1、比較的是操作符兩端的運算元是否是同一個物件。
2、兩邊的運算元必須是同一型別的(可以是父子類之間)才能編譯通過。
3、引用型別比較的是地址(即是否指向同一個物件),基本資料型別比較的是值,值相等則為true,如:int a=10 與 long b=10L 與 double c=10.0都是相同的(為true),因為他們都指向地址為10的堆。

‘equals()’用來比較的是兩個物件是否相等,由於所有的類都是繼承自java.lang.Object類的,在Object中的基類中定義了一個equals的方法,這個方法的初始行為是比較物件的記憶體地址,但String類中重寫了equals方法,比較的是字串的內容,而不再是比較類在堆記憶體中的存放地址了。

總結:在沒有重寫equals方法的情況下,他們之間的比較還是基於他們在記憶體中的存放位置的地址值的,因為Object的equals方法也是用雙等號(==)進行比較的,所以比較後的結果跟雙等號(==)的結果相同。String類中重寫了equals方法,變成了字串內容的比較。

結合上面分析,總結如下:
    1.單獨使用””引號建立的字串都是常量,編譯期就已經確定儲存到Constant Pool中;
    2.使用new String(“”)建立的物件會儲存到heap中,是執行期新建立的;
    3.使用只包含常量的字串連線符如”aa” + “aa”建立的也是常量,編譯期就能確定,已經確定儲存到String Pool中,String pool中存有“aaaa”;但不會存有“aa”。
    4.使用包含變數的字串連線符如”aa” + s1建立的物件是執行期才建立的,儲存在heap中;只要s1是變數,不論s1指向池中的字串物件還是堆中的字串物件,執行期s1 + “aa”操作實際上是編譯器建立了StringBuilder物件進行了append操作後通過toString()返回了一個字串物件存在heap上。
    5.String s2 = “aa” + s1; String s3 = “aa” + s1; 這種情況,雖然s2,s3都是指向了使用包含變數的字串連線符如”aa” + s1建立的存在堆上的物件,並且都是s1 + “aa”。但是卻指向兩個不同的物件,兩行程式碼實際上在堆上new出了兩個StringBuilder物件來進行append操作。在Thinking in java一書中285頁的例子也可以說明。
    6.對於final String s2 = “111”。s2是一個用final修飾的變數,在編譯期已知,在執行s2+”aa”時直接用常量“111”來代替s2。所以s2+”aa”等效於“111”+ “aa”。在編譯期就已經生成的字串物件“111aa”存放在常量池中。