程式扎記: [ Java Essence ] 眼見不為憑 : 不偷工反加料 (那些字串二三事)

標籤

2011年2月27日 星期日

[ Java Essence ] 眼見不為憑 : 不偷工反加料 (那些字串二三事)


轉載自 這裡
前言 :
在Java中,要建立字串很方便 :
  1. String str = "Java";  
依Java命名慣例而言,String這個名稱首字大寫,無疑地應該是個類別,這代表了,str是參考至一個String的實例。有些書會說,這個寫法其實等同於以下的寫法,而且這樣有建立物件的明確語義 :
  1. String str = new String("Java");  
這並不是全部的事實。這句話比較像是 :
  1. String str1 = "Java";  
  2. String str2 = new String(str1);  
也就是先前的那行程式碼,其實JVM建立了兩個String實例。這意謂著,直接使用雙引號包括字元來建立字串,以及自行用new關鍵字建立字字串是不同的,這可以由以下的程式碼來驗證 :
  1. String str1 = "Java";  
  2. String str2 = new String(str1);  
  3. System.out.println(str1 == str2);  
==運算子會比較兩個參考名稱是否參考至同一物件,上面的程式片段會印出false,也就是str1與str2是參考至不同物件。直接使用雙引號包括字元來建立字串,JVM會自行在記憶體中使用一個字串池(String pool)來維護,只要雙引號含括的字元內容相同(序列相同,大小寫相同),無論在程式碼中出現幾次,在字串池中都只有一個實例! 下面這段程式碼可以驗證 :
  1. String str1 = "Java";  
  2. String str2 = "Java";  
  3. System.out.println(str1 == str2);  
這個程式碼片段會印出true,因為雙引號含括的內容都是Java這個字元序列,雖然程式碼中出現了兩次"Java",但在字串池中卻只有一個實例,只不過依程式碼所定義的,被str1與str2所參考著. 一般書籍都會說,要比較字串是否相等要使用equals()方法而不是==,這個意思是指比較字串所含字元序列的相等性,而非參考名稱所參考的記憶體位置相等性。下面這個程式碼會顯示true,因為str1與str2所參考之物件,其所含字元序列都相等 :
  1. String str1 = "Java";  
  2. String str2 = new String(str1);  
  3. System.out.println(str1.equals(str2)); // 結果為 true  
每個字串都是不可變動的,這表示你一旦建立字串,就不可以修改它的字元內容。字串的字元內容,是維護在String中的字元陣列中 :
  1. public final class String  
  2.     implements java.io.Serializable, Comparable, CharSequence {  
  3.     /** The value is used for character storage. */  
  4.     private final char value[];  
下面這段程式碼,只不過是將字串內含字元陣列給另一個字串在內部複製或參考 :
  1. public String(String original) {  
  2.         int size = original.count;  
  3.         char[] originalValue = original.value;  
  4.         char[] v;  
  5.         if (originalValue.length > size) {  
  6.             v = Arrays.copyOfRange(originalValue, off, off+size);  
  7.         } else {  
  8.             v = originalValue;  
  9.         }  
  10.         this.offset = 0;  
  11.         this.count = size;  
  12.         this.value = v;  
  13.     }  
其它的字串建構式也是類似的。String上還有個intern()方法,可以讓你將字串放入字串池,或者是從字串池中取得JVM所維護的字串。如果你呼叫它,則會使用equals()方法,比較字串池中是否有字元序列相同的字串,如果有則傳回,如果無則將該字串置入字串池. 所以下面這個程式碼執行結果會是true :
  1. String str1 = "Java";  
  2. String str2 = new String(str1);  
  3. System.out.println(str1 == str2.intern());  
在Java中,可以使用+串接字串,例如 :
  1. String str1 = "Java";  
  2. String str2 = "Cool";  
  3. String str3 = str1 + str2;  
  4. System.out.println(str3);  
最後顯示的是JavaCool. 用+串接字串很方便,但要小心+會產生新的字串。這也不是全部的事實,因為它產生的更多,如果使用的是JDK5以上,可以實際反組譯看看 :
  1. String s = "Java";  
  2. String s1 = "Cool";  
  3. String s2 = (new StringBuilder()).append(s).append(s1).toString();  
  4. System.out.println(s2);  
如果是JDK1.4以下,則會在JVM內部產生StringBuffer完成類似的字串附加動作。StringBuilder或StringBuffer, 內部也是使用自動增加的字元陣列來維護,若長度不夠,則會產生新的更長的字元陣列,然後作字元陣列複製的動作。所以若是有頻繁串接字串的動作,例如在迴圈 中串接SQL之類的,會有效能上的隱憂,應當避免! 不過下面這個稍微有點不同 :
  1. String str = "Java" + "Cool";  
  2. System.out.println(str);  
執行結果一樣顯示JavaCool,不過反組譯它,你會發現編譯器很聰明 :
  1. String s = "JavaCool";  
  2. System.out.println(s);  
既然兩個都是雙引號括著,又直接使用+串接,那你要的不就是"JavaCool" ? 附帶一提的是,字串與物件之間也是可以使用+串接的!例如 :
  1. Map map = new HashMap();  
  2. map.put("key""value");  
  3. System.out.println("Map: " + map);  
這沒什麼!如果字串與物件使用+串接,其實最後會呼叫物件的toString()取得物件的字串描述,也就是說類似於 :
  1. Map map = new HashMap();  
  2. map.put("key""value");  
  3. System.out.println("Map: " + map.toString());  
這也不完全是事實,上面只是比喻!編譯器(或JVM)作的更多:
  1. HashMap hashmap = new HashMap();  
  2. hashmap.put("key""value");  
  3. System.out.println(  
  4.         (new StringBuilder()).append("Map: ").append(hashmap).toString());  
至於StringBuilder的append()作了什麼,留待你自行去探索一下它的原始碼吧!

補充說明 :
延伸閱讀 String and memory leaks
Probably most of the Java users is aware that String object is more complex than just an array of char. To make the usage of strings in Java more robust additional measures were taken – for instance the String pool was created to save memory by reusing the same String objects instead of allocating new ones. Another optimization that I want to talk about today is adding the offset and count fields to the String object instances...

[ Java 小學堂 ] Java 世界裡 equals 與 == 的差別
測試物件參考時,唯有當兩個參考指向同一物件時,==運算子的結果才為true,同樣的,唯有當兩個參考指向不同物件時,!=運算子的結果才為true,這兩個運算子的處理與物件的內容無關。需要特別注意的是,在String中使用==,因為Java為節省記憶體,會在某一輪調區中維護唯一的String物件,所以如果在類別裡使用同一字串,Java只會建立一個唯一的字串而已...
This message was edited 4 times. Last update was at 28/02/2011 14:43:37

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!