2016-11-23

Java String 有個 Pool!

從一個自以為是常識的問題開始:
String a = "java";
String b = "java";
System.out.println(a.equals(b));
這當然是 true 啦!

那這樣呢?
System.out.println(a == b);
false 囉,還用說!

錯,是 true!

Java 為了有限的記憶體空間,在某些條件下會將 String 暫存到 String Pool。

以前 Java 將 String Pool 放在 PermGen 空間,在 Java 7 以後就改放到空間較大的 Heap Space。

使用 String literal 建立的字串就符合這個條件。
String a = "java"; 
執行第一行程式時,String Pool 還是空的,所以 Java 就建立一個 "java" String 物件,並放到 String Pool 裡。
String b = "java"; 
來到第二行程式時,Java 在 String Pool 裡找到了第一行程式建立的 "java" String 物件,就直接拿來用,所以才會出現 a == b 為 true 的情況發生,因為不只值相同,根本就是同一個物件。

那改用 new String 取代 String literal 方式來建立 String 物件,結果是一樣的嗎?
String c = new String("java");
String d = new String("java");
System.out.println(c.equals(d));
System.out.println(c == d); // false
結果不一樣,equals 是 true 沒有問題,但是 == 卻是 false。

疑?不是有 String Pool?

很抱歉,new String 不符合剛說的「某些條件」,所以 new String 不會去 Pool 找「值相同」的物件,而是直接建立全新的 "java" String 物件。

還有一點很重要的是,new String 建立的 String 物件不會放到 Pool 裡。
String a = new String("java");
String b = "java";
System.out.println(a == b); // false
可以從上面的結果(false)看出來,因為 a 沒放到 Pool 裡,所以 b 等於是全新的。

但 Java 為此留下彈性空間,透過呼叫 String 物件的 intern() 就可以手動將 String 物件放到 String Pool。
String a = new String("java");
String b = a.intern();
String c = "java";
System.out.println(b == c); // true
呼叫 intern() 後,Java 將 a 複製一份放到 String Pool,然後回傳這個複製的版本。

因此在 intern() 後透過 String literal 建立的 String 物件,就會是同一個 String 物件。

要注意剛說的,是複製不是直接把 a 放進去,可以比對 a 與 intern() 回傳的 b,就可以知道是不同的物件。
System.out.println(a == b); // false
其實剛講的複製只說了一半,正確來說 intern() 是先到 String Pool 找值相同的物件,如果有就拿來用,如果沒有才複製一份放進去。

可以對 a 連續呼叫兩次 intern() 看出端倪。
String a = new String("java");
String b = a.intern();
String c = a.intern();
System.out.println(b == c); // true
第一次呼叫 intern() 時,由於沒找到值相同的 String 物件,所以複製一份放進去然後回傳 b。

第二次呼叫 intern() 時,找到值相同的 b 然後回傳得到 c,所以 b == c 為 true。

intern() 的行為是不是和 String literal 很像?先找再建立。

沒錯,Java 偷偷為 String literal 呼叫了 intern()。


----------------------------------------------------------------------
講半天,這些對實務上到底有什麼幫助?

首先,一律用 equals 比較 String 物件的值是否相等,不要用 ==。

這樣就可以避開 String Pool 造成的困擾。

再來,在進行 String 物件串接時,一律用 StringBuilder,不要用 "java" + "8" 這種方式。

因為後者會在 String Pool 增加無謂的暫存,容易耗費記憶體。


----------------------------------------------------------------------
最後總結一下 String literal 與 new String 的差異:

String literal 先從 String Pool 找值相同的 String 物件,new String 則是直接建立全新的 String 物件。

String literal 會自動呼叫 intern(),new String 不會,但可以手動呼叫(誰會做這個事?)。
---
---
---

沒有留言:

張貼留言