前言:
字符串 (String) 是 java 編程語言中的核心類之一,在我們平常時候使用也比較很普遍,應用廣泛。
但你是否知道什么是字符串直接量,知不知道有個字符串駐留池,字符串的駐留池可以用來緩存字符串直接量。
由 synchronized 修飾的方法可以保證方法的線程安全,但是會降低該方法的執行效率; 翻開 API,很容易就能知道:StringBuffer 是線程安全的可變字符序列,StringBuilder 是一個可變的字符序列,是線程不安全的;
什么是直接量?
直接量是指:在程序中,通過源代碼直接指定的值。
eg:
int personId = 8080 ;
String name = "fancy" ;
對于 java 中的字符串直接量,JVM 會使用一個字符串駐留池來緩存它們。一般情況下,字符串駐留池中的字符串對象不會被 GC (Garbage Collection,垃圾回收) 所回收,
當再次使用字符串駐留池中已有的字符串對象時候,無需再次創建它,而直接使它的引用變量指向字符串駐留池中已有的字符串對象。
String 基礎:
String
類代表字符串。字符串是常量,它們的值在創建之后是不能再被更改的。在 java 中除了 synchronized之外,不可變的類也是線程安全的,
因此,String 類本身也是線程安全的。String 類的實例對象其實是可以被共享的。
示例代碼:
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()
才為 tr
ue
。 ”
示例代碼:
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
輸出結果:
2not equals 1
3equals 2
4
由上面的示例代碼可以看到,字符串駐留池的使用是非常簡單的,池中的對象可以被共享,只要你將字符串添加到池中,就能夠直接使用
"==" 來比較兩個對象,而不是只能使用 equals 來作比較。將字符串添加到駐留池來使用 "==" 作比較的方式要比直接使用 equals 效率要高些。
再論 String、StringBuffer 和 StringBuilder
網上說的所謂的使用 StringBuffer 的效率更高更好,這已經不合時宜,這是 java 1.5 之前的版本的說法,早過時了現在!!
現在是反過來了,由于 StringBuilder 不是線程安全的,StringBuilder 效率會比 StringBuffer 效率更高一些。
你可以不相信我說的,但你總該相信程序跑出來的結果吧,下面是示例代碼:
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
后臺輸出結果:
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" 對象。
這樣解釋都能夠理解了吧?真的理解了嗎?是真的理解才好,不妨再來看一個:
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 對象。