http://www.javaeye.com/post/199106

?要理解 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字符,那么雙引號中字符的數量會更少(一個中文字符占用三個字節)。如果超出這個數量,在編譯的時候編譯器會報錯。