要理解 java中String的運作方式,必須明確一點:String是一個非可變類(immutable)。什麼是非可變類呢?簡單說來,非可變類的執行個體是不能被修改的,每個執行個體中包含的資訊都必須在該執行個體建立的時候就提供出來,並且在對象的整個生存周期內固定不變。java為什麼要把String設計為非可變類呢?你可以問問 james Gosling :)。但是非可變類確實有著自身的優勢,如狀態單一,對象簡單,便於維護。其次,該類對象對象本質上是安全執行緒的,不要求同步。此外使用者可以共用非可變對象,甚至可以共用它們的內部資訊。(詳見 《Effective java》item 13)。String類在java中被大量運用,甚至在class檔案中都有其身影,因此將其設計為簡單輕便的非可變類是比較合適的。
一、建立。
好了,知道String是非可變類以後,我們可以進一步瞭解String的構造方式了。建立一個Stirng對象,主要就有以下兩種方式:
java 代碼
1.String str1 = new String("abc");
2.Stirng str2 = "abc";
雖然兩個語句都是返回一個String對象的引用,但是jvm對兩者的處理方式是不一樣的。對於第一種,jvm會馬上在heap中建立一個String對象,然後將該對象的引用返回給使用者。對於第二種,jvm首先會在內部維護的strings pool中通過String的 equels 方法尋找是對象池中是否存放有該String對象,如果有,則返回已有的String對象給使用者,而不會在heap中重新建立一個新的String對象;如果對象池中沒有該String對象,jvm則在heap中建立新的String對象,將其引用返回給使用者,同時將該引用添加至strings pool中。注意:使用第一種方法建立對象時,jvm是不會主動把該對象放到strings pool裡面的,除非程式調用 String的intern方法。看下面的例子:
java 代碼
1.String str1 = new String("abc"); //jvm 在堆上建立一個String對象
2.
3. //jvm 在strings pool中找不到值為“abc”的字串,因此
4. //在堆上建立一個String對象,並將該對象的引用加入至strings pool中
5. //此時堆上有兩個String對象
6.Stirng str2 = "abc";
7.
8. if(str1 == str2){
9. System.out.println("str1 == str2");
10. }else{
11. System.out.println("str1 != str2");
12. }
13. //列印結果是 str1 != str2,因為它們是堆上兩個不同的對象
14.
15. String str3 = "abc";
16. //此時,jvm發現strings pool中已有“abc”對象了,因為“abc”equels “abc”
17. //因此直接返回str2指向的對象給str3,也就是說str2和str3是指向同一個對象的引用
18. if(str2 == str3){
19. System.out.println("str2 == str3");
20. }else{
21. System.out.println("str2 != str3");
22. }
23. //列印結果為 str2 == str3
再看下面的例子:
java 代碼
1.String str1 = new String("abc"); //jvm 在堆上建立一個String對象
2.
3.str1 = str1.intern();
4.//程式顯式將str1放到strings pool中,intern運行過程是這樣的:首先查看strings pool
5.//有沒“abc”對象的引用,沒有,則在堆中建立一個對象,然後將新對象的引用加入至
6.//strings pool中。執行完該語句後,str1原來指向的String對象已經成為垃圾對象了,隨時會
7.//被GC收集。
8.
9.//此時,jvm發現strings pool中已有“abc”對象了,因為“abc”equels “abc”
10.//因此直接返回str1指向的對象給str2,也就是說str2和str1引用著同一個對象,
11.//此時,堆上的有效對象只有一個。
12.Stirng str2 = "abc";
13.
14. if(str1 == str2){
15. System.out.println("str1 == str2");
16. }else{
17. System.out.println("str1 != str2");
18. }
19. //列印結果是 str1 == str2
20.
為什麼jvm可以這樣處理String對象呢?就是因為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字元,那麼雙引號中字元的數量會更少(一個中文字元佔用三個位元組)。如果超出這個數量,在編譯的時候編譯器會報錯。