java String 以及字符串直接量 與 字符串駐留池 … – JAVA編程語言程序開發技術文章

字符串 (String) 是 java 編程語言中的核心類之一,在我們平常時候使用也比較很普遍,應用廣泛。
但你是否知道什麼是字符串直接量,知不知道有個字符串駐留池,字符串的駐留池可以用來緩存字符串直接量。

什麼是直接量?
直接量是指:在程序中,通過源代碼直接指定的值。
eg:
int personId = 8080 ;
String name = "fancy" ;

對於 java 中的字符串直接量,JVM 會使用一個字符串駐留池來緩存它們。一般情況下,字符串駐留池中的字符串對象不會被 GC (Garbage Collection,垃圾回收) 所回收,
當再次使用字符串駐留池中已有的字符串對象時候,無需再次創建它,而直接使它的引用變量指向字符串駐留池中已有的字符串對象。

String 基礎:

String 類代表字符串。字符串是常量,它們的值在創建之後是不能再被更改的。在 java 中除瞭 synchronized之外,不可變的類也是線程安全的,
因此,String 類本身也是線程安全的。String 類的實例對象其實是可以被共享的。

示例代碼:

1
2        String name = "fancy";                  // @1
3        String nick    = "fancydeepin";  // @2
4        name = nick;
5        System.out.println(name);    // export:fancydeepin
6

結果輸出 :fancydeepin

這是怎麼回事?不是說 String 是不可變的字符串嗎?怎麼這裡又變瞭?
是這樣的,在這裡 name 隻是一個引用類型變量,並不是一個 String 對象,@1中創建瞭一個 "fancy" 的字符串對象,
@2中創建瞭一個 "fancydeepin" 的字符串對象,name 引用 (就像一個指針) 剛開始是指向 "fancy" 對象,而後,name 又重新指向 "fancydeepin" 對象,
在示例代碼中,整個過程隻創建瞭兩個 String 對象 (不知道我這樣說你能不能理解,為什麼是隻創建瞭兩個 String 對象?而不是 1個、3個…  @3),
一個是 "fancy" 對象,另外一個是 "fancydeepin" 對象。而這兩個對象被創建出來後並沒有被改變過,之所以程序會輸出 fancydeepin,完全隻是因為
name 引用所指向的對象發生瞭改變。

如果你是本著認真的態度看著我的貼子,細心的你,是否會留意到:
當 name 引用重新指向另外一個對象的時候,那 name 之前引用的對象 ( "fancy" 對象 ) JVM 在底層會怎麼處理它呢?是會立即來回收它來釋放系統資源嗎?
答案是否定的。雖然這時候程序再也不訪問 "fancy" 這個對象,但 JVM 還是不會來回收它,它將在程序運行期間久駐內存,為什麼會這樣呢?
再往下說就扯到 java 的內存管理機制瞭,這裡點到即止。在這裡你可以簡單的將它理解成 "fancy" 對象被緩存瞭起來 ( 實際上也是因為被緩存瞭 )。

字符串駐留池
當比較兩個 String 對象時候,是應該用 "==" 呢?還是應該選擇 equals 呢?相信絕大部分人絕大多時候使用的都是選擇用 equals 方法。
"==" 和 equals 的用法相信大傢都很熟悉瞭,"==" 比較的是兩個對象的哈希碼值是否相等,而 equals 比較的是對象的內容是否一樣。
而絕大部分時候我們比較兩個 String 對象的時候隻是想比較它們的內容是否相等,這樣看來,隻能選 equals 瞭,但真的是這樣嗎?
答案是否定的。你一樣也可以用 "==" 來完成這樣的一件事情,而且 "==" 的效率無論如何都是要比使用 equals 的效率要高的,但前提是,
需要使用字符串的駐留池,才能使用 "==" 來替代 equals 作比較。
String 裡面有個方法叫 intern(),執行效率很高,但也許你還不曾用過,下面是摘自API中 intern() 方法的描述:

“當調用 intern 方法時,如果池已經包含一個等於此 String 對象的字符串(用 equals(Object) 方法確定),則返回池中的字符串。
否則,將此 String 對象添加到池中,並返回此 String 對象的引用。 它遵循以下規則:
對於任意兩個字符串 s 和 t,當且僅當 s.equals(t) 為 true 時,s.intern() == t.intern() 才為 true。 ”

示例代碼:

 1
 2             String name = new String("fancy");
 3           
 4        if(name == "fancy") {    // false
 5            System.out.println("equals 1");
 6        }else {
 7            System.out.println("not equals 1");    // Be printed
 8        }
 9       
10        name = name.intern();    // 將字符串添加到駐留池
11       
12        if(name == "fancy") {    // true
13            System.out.println("equals 2");        // Be printed
14        }else {
15            System.out.println("not equals 2");
16        }
17

輸出結果:

1
2not equals 1
3equals 2
4
 

由上面的示例代碼可以看到,字符串駐留池的使用是非常簡單的,池中的對象可以被共享,隻要你將字符串添加到池中,就能夠直接使用
"==" 來比較兩個對象,而不是隻能使用 equals 來作比較。將字符串添加到駐留池來使用 "==" 作比較的方式要比直接使用 equals 效率要高些。

再論 String、StringBuffer 和 StringBuilder

由 synchronized 修飾的方法可以保證方法的線程安全,但是會降低該方法的執行效率;

翻開 API,很容易就能知道:StringBuffer 是線程安全的可變字符序列,StringBuilder 是一個可變的字符序列,是線程不安全的;
網上說的所謂的使用 StringBuffer 的效率更高更好,這已經不合時宜,這是 java 1.5 之前的版本的說法,早過時瞭現在!!
現在是反過來瞭,由於 StringBuilder 不是線程安全的,StringBuilder 效率會比 StringBuffer 效率更高一些。

你可以不相信我說的,但你總該相信程序跑出來的結果吧,下面是示例代碼:

 1
 2        StringBuffer  buffer  = new StringBuffer();
 3        StringBuilder builder = new StringBuilder();
 4        int COUNT = 10;       // 測試 COUNT 趟
 5        final int N = 100000; // 每趟操作 N 次
 6        double beginTime, costTime; // 每趟開始時間和耗費時間
 7        double bufferTotalTime = 0.0D, buliderTotalTime = 0.0D; // StringBuffer 和 StringBuilder 測試 COUNT 趟的總耗時
 8        while(COUNT — > -1) {
 9            // 也可以測試每趟都創建一個新的對象,這樣 StringBuilder 效率比 StringBuffer 的效率變得更明顯瞭
10            /**
11            StringBuffer  buffer  = new StringBuffer();
12            StringBuilder builder = new StringBuilder();
13            */
14            System.out.println("———————————-<" + (COUNT + 1) + ">");
15            beginTime = System.currentTimeMillis();
16            for(int i = 0; i < N; i++) {
17                buffer.append(i);
18                buffer.length();
19            }
20            costTime = System.currentTimeMillis() – beginTime;
21            bufferTotalTime  += costTime;
22            System.out.println("StringBuffer  費時: —>> " + costTime);
23            beginTime = System.currentTimeMillis();
24            for(int i = 0; i < N; i++) {
25                builder.append(i);
26                builder.length();
27            }
28            costTime = System.currentTimeMillis() – beginTime;
29            buliderTotalTime += costTime;
30            System.out.println("StringBuilder 費時: —>> " + costTime);
31            System.out.println("———————————-<" + (COUNT + 1) + ">");
32        }
33        System.out.println("bufferTotalTime / buliderTotalTime = " + (bufferTotalTime / buliderTotalTime));
34

後臺輸出結果:

 1
 2———————————-<10>
 3StringBuffer  費時: —>> 32.0
 4StringBuilder 費時: —>> 16.0
 5———————————-<10>
 6———————————-<9>
 7StringBuffer  費時: —>> 21.0
 8StringBuilder 費時: —>> 15.0
 9———————————-<9>
10———————————-<8>
11StringBuffer  費時: —>> 25.0
12StringBuilder 費時: —>> 35.0
13———————————-<8>
14———————————-<7>
15StringBuffer  費時: —>> 24.0
16StringBuilder 費時: —>> 8.0
17———————————-<7>
18———————————-<6>
19StringBuffer  費時: —>> 48.0
20StringBuilder 費時: —>> 38.0
21———————————-<6>
22———————————-<5>
23StringBuffer  費時: —>> 22.0
24StringBuilder 費時: —>> 8.0
25———————————-<5>
26———————————-<4>
27StringBuffer  費時: —>> 23.0
28StringBuilder 費時: —>> 9.0
29———————————-<4>
30———————————-<3>
31StringBuffer  費時: —>> 25.0
32StringBuilder 費時: —>> 7.0
33———————————-<3>
34———————————-<2>
35StringBuffer  費時: —>> 23.0
36StringBuilder 費時: —>> 7.0
37———————————-<2>
38———————————-<1>
39StringBuffer  費時: —>> 78.0
40StringBuilder 費時: —>> 59.0
41———————————-<1>
42———————————-<0>
43StringBuffer  費時: —>> 21.0
44StringBuilder 費時: —>> 11.0
45———————————-<0>
46bufferTotalTime / buliderTotalTime = 1.6056338028169015
47

StringBuffer 在測試中平均耗時是 StringBuilder 的 1.6 倍以上,再測多次,都是 1.6 倍以上。StringBuffer 和 StringBuilder 的性能孰更加優,一眼明瞭。

最後,如果你想知道 @3 (在上面我已經用紅色粗體標出) 的結果,不妨說一下:
eg:
String mail = "fancydeepin" + "@" + "yeah.net";

來,一起來看一下上面的這條語句,想一下,這條語句將會創建幾個 String 的對象呢?
1個? 2個? 3個? 4個? 5個? … …

也許你會認為是4個,它們分別是:"fancydeepin"、"@"、"yeah.net"、"fancydeepin@yeah.net"
也許你會認為是5個,它們分別是:"fancydeepin"、"@"、"yeah.net"、"fancydeepin@"、"fancydeepin@yeah.net"
也許 … …

但實際上,這條語句隻創建瞭一個 String 對象!
為什麼會這樣呢?原因很簡單,這是因為,mail 在 編譯的時候其值就已經確定,它就是 "fancydeepin@yeah.net" 。
當程序處於運行期間且當上面的這條語句被執行到的時候,那麼 mail 所引用的對象就會被創建,而 mail 的值由於在編譯的時候已經確定
它是  "fancydeepin@yeah.net" ,所以最終隻有一個 String 對象被創建出來,而這個對象就是  "fancydeepin@yeah.net" 對象。www.aiwalls.com

這樣解釋都能夠理解瞭吧?真的理解瞭嗎?是真的理解才好,不妨再來看一個:
eg:
String mail = new String("fancydeepin@yeah.net");

這回又創建瞭幾個對象呢?
答案是2個。為什麼不是1個瞭呢?2個又是哪2個呢?
可以很肯定的告訴你,它們分別是: "fancydeepin@yeah.net"、new String()
這是因為,這回 mail 在編譯的時候它的值是還不能夠確定的,編譯隻是將源代碼翻譯成字節碼,程序還並沒有跑起來,還 new 不瞭對象,
所以在編譯完成之後,mail 的值是還不能夠確定的。
當程序處於運行期間且當上面的這條語句被執行到的時候,這時候才開始去確定 mail 的引用對象,首先,"fancydeepin@yeah.net" 對象會被創建,
之後,再執行 new String(),所以這條語句最後實際上是創建瞭 2個 String 對象。

 作者:fancydeepin

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *