String 和StringBuffer的區別?
2、String 和 StringBuffer的區別。
這是繼int 和 Integer的區別的由一個有趣的問題。
在int 和 Integer的區別中,我們知道那是java基本資料型別和包裝器類的相關知識點。但是String不是基本資料型別,java又是提供什麼樣的機制來處理字串型別的呢?
String物件是不可變(只讀)的字元序列:字串在建立之後,其內容就永遠不能再修改了。所以Java語言還提供了StringBuilder類來處理可變的字串。而StringBuffer類基本上等同於StringBuilder類,它們之間只存在一處差異:StringBuffer類可追加的字元序列提供了執行緒安全的(thread-safe)實現。下面引用chinaitlab上面的一篇文章來進行詳細說明,文字來源:
String和StringBuffer之概覽
非可變物件一旦建立之後就不能再被改變,可變物件則可以在建立之後被改變。String物件是非可變物件,StringBuffer物件則是可變物件。為獲得更佳的效能你需要根據實際情況小心謹慎地選擇到底使用這兩者中的某一個。下面的話題會作詳細的闡述。(注意:這個章節假設讀者已經具備Java的String和StringBuffer的相關基礎知識。)
建立字串的較佳途徑
你可以按照以下方式建立字串物件:
1. String s1 = "hello";
String s2 = "hello";
2. String s3 = new String("hello");
String s4 = new String("hello");
上面哪種方式會帶來更好的效能呢?下面的程式碼片斷用來測量二者之間的區別。
StringTest1.java
package com.performance.string;
/** This class shows the time taken for creation of
* String literals and String objects.
*/
public class StringTest1 {
public static void main(String[] args){
// create String literals
long startTime = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s1 = "hello";
String s2 = "hello";
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken for creation of String literals : "
+ (endTime - startTime) + " milli seconds" );
// create String objects using 'new' keyword
long startTime1 = System.currentTimeMillis();
for(int i=0;i<50000;i++){
String s3 = new String("hello");
String s4 = new String("hello");
}
long endTime1 = System.currentTimeMillis();
System.out.println("Time taken for creation of String objects : "
+ (endTime1 - startTime1)+" milli seconds");
}
}
這段程式碼的輸出:
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects : 170 milli seconds
JVM是怎樣處理字串的呢?
所以結論是,JVM會內在地為字串字面量維護一些唯一的String物件,程式設計師不需要為字串字面量而發愁,但是可能會被一些通過 new關鍵字建立的String物件而困擾,不過他們可以使用intern()方法來避免在堆記憶體上建立重複的String物件來改善Java的執行效能。下一小節會向大家展示更多的資訊。
下圖展示了未使用intern()方法來建立字串的情況。
string_creating_without_intern() method
你可以自己使用==操作符和String.equals()方法來編碼測試上面提到的區別。==操作符會返回true如果一些引用指向一個相同的物件但不會判斷String物件的內容是否相同;String.equals()方法會返回true如果被操作的String物件的內容相同。對於上面的程式碼會有s1==s2,因為s1和s2兩個引用指向同一個物件,對於上面的程式碼,s3.equals(s4)會返回true因為兩個物件的內容都一樣為”hello”。你可以從上圖看出這種機制。在這裡有三個獨立的包含了相同的內容(”hello”)的物件,實際上我們不需要這麼三個獨立的物件—— 因為要執行它們的話既浪費時間又浪費記憶體。
那麼怎樣才能確保String物件不會重複呢?下一個話題會涵蓋對於內建String機制的興趣。
滯留字串的優化作用
同一個字串物件被重複地建立是不必要的,String.intern ()方法可以避免這種情況。下圖說明了String.intern()方法是如何工作的,String.intern()方法檢查字串物件的存在性,如果需要的字串物件已經存在,那麼它會將引用指向已經存在的字串物件而不是重新建立一個。下圖描繪了使用了intern()方法的字串字面量和字串物件的建立情況。
string_creating_with_intern() method
下面的例程幫助大家瞭解String.intern()方法的重要性。
StringTest2.java
package com.performance.string;
// This class shows the use of intern() method to improve performance
public class StringTest2 {
public static void main(String[] args){
// create String references like s1,s2,s3...so on..
String variables[] = new String[50000];
for( int i=0;i<variables.length;i++){
variables[i] = "s"+i;
}
// create String literals
long startTime0 = System.currentTimeMillis();
for(int i=0;i<variables.length;i++){
variables[i] = "hello";
}
long endTime0 = System.currentTimeMillis();
System.out.println("Time taken for creation of String literals : "
+ (endTime0 - startTime0) + " milli seconds" );
// create String objects using 'new' keyword
long startTime1 = System.currentTimeMillis();
for(int i=0;i<variables.length;i++){
variables[i] = new String("hello");
}
long endTime1 = System.currentTimeMillis();
System.out.println("Time taken for creation of String objects with 'new' key word : "
+ (endTime1 - startTime1)+" milli seconds");
// intern String objects with intern() method
long startTime2 = System.currentTimeMillis();
for(int i=0;i<variables.length;i++){
variables[i] = new String("hello");
variables[i] = variables[i].intern();
}
long endTime2 = System.currentTimeMillis();
System.out.println("Time taken for creation of String objects with intern(): "
+ (endTime2 - startTime2)+" milli seconds");
}
}
這是上面那段程式碼的輸出結果:
Time taken for creation of String literals : 0 milli seconds
Time taken for creation of String objects with 'new' key word : 160 milli seconds
Time taken for creation of String objects with intern(): 60 milli seconds
連線字串時候的優化技巧
你可以使用+操作符或者String.concat()或者StringBuffer.append()等辦法來連線多個字串,那一種辦法具有最佳的效能呢?
如何作出選擇取決於兩種情景,第一種情景是需要連線的字串是在編譯期決定的還是在執行期決定的,第二種情景是你使用的是 StringBuffer還是String。通常程式設計師會認為StringBuffer.append()方法會優於+操作符或 String.concat()方法,但是在一些特定的情況下這個假想是不成立的。
1) 第一種情景:編譯期決定相對於執行期決定
請看下面的StringTest3.java程式碼和輸出結果。
package com.performance.string;
/** This class shows the time taken by string concatenation at compile time and run time.*/
public class StringTest3 {
public static void main(String[] args){
//Test the String Concatination
long startTime = System.currentTimeMillis();
for(int i=0;i<5000;i++){
String result = "This is"+ "testing the"+ "difference"+ "between"+
"String"+ "and"+ "StringBuffer";
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken for string concatenation using + operator : "
+ (endTime - startTime)+ " milli seconds");
//Test the StringBuffer Concatination
long startTime1 = System.currentTimeMillis();
for(int i=0;i<5000;i++){
StringBuffer result = new StringBuffer();
result.append("This is");
result.append("testing the");
result.append("difference");
result.append("between");
result.append("String");
result.append("and");
result.append("StringBuffer");
}
long endTime1 = System.currentTimeMillis();
System.out.println("Time taken for String concatenation using StringBuffer : "
+ (endTime1 - startTime1)+ " milli seconds");
}
}
這是上面的程式碼的輸出結果:
Time taken for String concatenation using + operator : 0 milli seconds
Time taken for String concatenation using StringBuffer : 50 milli seconds
很有趣地,+操作符居然比StringBuffer.append()方法要快,為什麼呢?
這裡編譯器的優化起了關鍵作用,編譯器像下面舉例的那樣簡單地在編譯期連線多個字串。它使用編譯期決定取代執行期決定,在你使用new關鍵字來建立String物件的時候也是如此。
編譯前:
String result = "This is"+"testing the"+"difference"+"between"+"String"+"and"+"StringBuffer";
編譯後:
String result = "This is testing the difference between String and StringBuffer";
這裡String物件在編譯期就決定了而StringBuffer物件是在執行期決定的。執行期決定需要額外的開銷當字串的值無法預先知道的時候,編譯期決定作用於字串的值可以預先知道的時候,下面是一個例子。
編譯前:
public String getString(String str1,String str2) {
return str1+str2;
}
編譯後:
return new StringBuffer().append(str1).append(str2).toString();
執行期決定需要更多的時間來執行。
2) 第二種情景:使用StringBuffer取代String
看看下面的程式碼你會發現與情景一相反的結果——連線多個字串的時候StringBuffer要比String快。
StringTest4.java
package com.performance.string;
/** This class shows the time taken by string concatenation
using + operator and StringBuffer */
public class StringTest4 {
public static void main(String[] args){
//Test the String Concatenation using + operator
long startTime = System.currentTimeMillis();
String result = "hello";
for(int i=0;i<1500;i++){
result += "hello";
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken for string concatenation using + operator : "
+ (endTime - startTime)+ " milli seconds");
//Test the String Concatenation using StringBuffer
long startTime1 = System.currentTimeMillis();
StringBuffer result1 = new StringBuffer("hello");
for(int i=0;i<1500;i++){
result1.append("hello");
}
long endTime1 = System.currentTimeMillis();
System.out.println("Time taken for string concatenation using StringBuffer : "
+ (endTime1 - startTime1)+ " milli seconds");
}
}
這是上面的程式碼的輸出結果:
Time taken for string concatenation using + operator : 280 milli seconds
Time taken for String concatenation using StringBuffer : 0 milli seconds
看得出StringBuffer.append()方法要比+操作符要快得多,為什麼呢?
原因是兩者都是在執行期決定字串物件,但是+操作符使用不同於StringBuffer.append()的規則通過String和StringBuffer來完成字串連線操作。(譯註:什麼樣的規則呢?)
藉助StringBuffer的初始化過程的優化技巧
你可以通過StringBuffer的建構函式來設定它的初始化容量,這樣可以明顯地提升效能。這裡提到的建構函式是StringBuffer(int length),length引數表示當前的StringBuffer能保持的字元數量。你也可以使用ensureCapacity(int minimumcapacity)方法在StringBuffer物件建立之後設定它的容量。首先我們看看StringBuffer的預設行為,然後再找出一條更好的提升效能的途徑。
StringBuffer的預設行為:
StringBuffer在內部維護一個字元陣列,當你使用預設的建構函式來建立StringBuffer物件的時候,因為沒有設定初始化字元長度,StringBuffer的容量被初始化為16個字元,也就是說預設容量就是16個字元。當StringBuffer達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,也就是(2*舊值+2)。
如果你使用預設值,初始化之後接著往裡面追加字元,在你追加到第16個字元的時候它會將容量增加到34(2*16+2),當追加到34個字元的時候就會將容量增加到70(2*34+2)。無論何事只要StringBuffer到達它的最大容量它就不得不建立一個新的字元陣列然後重新將舊字元和新字元都拷貝一遍——這也太昂貴了點。所以總是給StringBuffer設定一個合理的初始化容量值是錯不了的,這樣會帶來立竿見影的效能增益。
我利用兩個StringBuffer重新測試了上面的StringTest4.java程式碼,一個未使用初始化容量值而另一個使用了。這次我追加了50000個’hello’物件沒有使用+操作符。區別是我使用StringBuffer(250000)的建構函式來初始化第二個 StringBuffer了。
輸出結果如下:
Time taken for String concatenation using StringBuffer with out setting size: 280 milli seconds
Time taken for String concatenation using StringBuffer with setting size: 0 milli seconds
StringBuffer初始化過程的調整的作用由此可見一斑。所以,使用一個合適的容量值來初始化StringBuffer永遠都是一個最佳的建議。
關鍵點
1. 無論何時只要可能的話使用字串字面量來常見字串而不是使用new關鍵字來建立字串。
2. 無論何時當你要使用new關鍵字來建立很多內容重複的字串的話,請使用String.intern()方法。
3. +操作符會為字串連線提供最佳的效能——當字串是在編譯期決定的時候。
4. 如果字串在執行期決定,使用一個合適的初期容量值初始化的StringBuffer會為字串連線提供最佳的效能。