一、字串的連接效率問題
??Java當中對陣列初始化之后,該陣列所占的記憶體空間、陣列長度的都是不可變的,我們在撰寫程式的時候,對于陣列的初始化都需要給定長度才行,對于創建一個字串,為字串物件分配記憶體空間,會耗費掉一定的時間與空間的代價,也即CPU和記憶體的代價消耗,作為最基礎的資料型別,大量頻繁的創建字串,會極大程度地影響程式的性能,
??每次連接字串時都會創建一個新的String物件,隨著拼接次數的增多,這個物件會越來越大,比如說,進行100次拼接需要創建100個String物件才能夠達到目的,對于這點的詳細說明請見我的前期博客👉https://blog.csdn.net/pf6668/article/details/108760427👈
??接下來我們以代碼的形式來具體比較一下String、StringBuffer和StringBuilder三者對于字串拼接所體現出來的效率問題,代碼如下:
public class Test {
public static void main(String[] args) {
StringTest(10000);
StringBufferTest(10000);
StringBuilderTest(10000);
}
public static void StringTest(int n){
String str = "";
Long startTime = System.currentTimeMillis();
for(int i=0;i<n;i++){
str += i;
}
Long endTime = System.currentTimeMillis();
System.out.println("String 連接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
public static void StringBufferTest(int n){
StringBuffer str = new StringBuffer();
Long startTime = System.currentTimeMillis();
for(int i=0;i<n;i++){
str.append(i);
}
Long endTime = System.currentTimeMillis();
System.out.println("StringBuffer 連接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
public static void StringBuilderTest(int n){
StringBuilder str = new StringBuilder();
Long startTime = System.currentTimeMillis();
for(int i=0;i<n;i++){
str.append(i);
}
Long endTime = System.currentTimeMillis();
System.out.println("StringBuilder 連接"+ n +"次消耗"+(endTime-startTime)+"ms");
}
}
運行結果如下:

??我們可以清楚的看到String的字串的連接效率是最低的,這一點對于大量字串的拼接可以很明顯的表示出來,所以說大量字串的拼接最好不要選擇String,StringBuffer和StringBuilder對于字串的拼接效率是大致相同的,
??另外需要指出來的一點就是,“+”對于字串的拼接操作底層到底是怎么實作的呢?這個問題值得我們去探討,我們再來看看另外一段代碼:
public class StringTest {
public static void main(String[] args) {
String a = "abc";
String b = "def";
String c = a + b;
String d = "abc" + "def";
System.out.Println(c);
System.out.Println(d);
}
}
很明顯輸出的結果都是abcdef,那么直接用靜態的字串連接以及用字串型別的變數進行連接有何不同呢?我們可以通過其編譯的代碼看到其中的不同,
1.如果是直接用靜態字串進行連接的話,JVM會認為字串之間的連接“+”是沒有用的,就會在編譯階段直接把這個“+”省略而把字串連接起來,也就是說在編譯期是有優化的,編譯期生成的位元組碼已經是拼接之后的結果,這個時候性能是很高的,
2.如果使用變數進行連接的話,“+”拼接的底層是用StringBuilder來實作的,連接的程序就是StringBuilder使用append方法進行連接的程序,最后再呼叫toString()方法轉為字串,
對于String+的方式進行字串的連接,每回圈一次,就會重新new一個StringBuilder物件,這對記憶體的消耗是極大的,這也是進行“+”拼接字串效率不高的最主要的原因,
二、為什么StringBuffer和StringBuilder的執行效率高
??String在java中是不可變長的,一旦初始化就不能修改長度,簡單的字串拼接其實是創建新的String物件,再把拼接后的內容賦值給新的物件,在頻繁修改的情況下會頻繁創建物件,而StringBuilder則不會,從頭到尾只有一個實體物件,那StringBuilder是怎么實作的呢?
??StringBuilder在進行append連接字串的時候并不是用String存盤,而是存放到一個名為value的char陣列當中,字串是固定長度的,而陣列是可以擴容的,這樣就不需要不停創建物件了,


??從以上兩張圖片我們就可以看出來為什么String對字串的操作不可變了,那StringBuilder中陣列的初始長度是多少呢?擴容系數是多少呢?我們來看下面的原始碼圖片:


??陣列默認的初始長度是16,擴容系數是value.length * 2 + 2,也即 (value.length << 1) + 2,而且只有當append之后的資料長度大于value.length時才會擴容一次,并不是每次連接都會進行擴容操作,
三、StringBuilder和StringBuffer的執行緒安全比較
??對于這個問題我們還是從JDK原始碼分析入手來看:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
StringBuffer:執行緒安全;
StringBuilder:執行緒不安全,
??因為StringBuffer的所有公開方法都是synchronized修飾的,而StringBuilder并沒有synchronized修飾,
??除了執行緒安全問題,StringBuilder和StringBuffer還有另外兩點需要比較的地方:
1.緩沖區問題
//StringBuilder的toString()方法
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
//StringBuffer的toString()方法
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
??StringBuffer每次獲取toString都會直接使用快取區的 toStringCache值來構造一個字串,StringBuilde 則每次都需要復制一次字符陣列,再構造一個字串,所以說StringBuffer利用快取區進行了部分優化操作,
??StringBuffer中比StringBuilder多了一個toStringCache欄位,欄位上的解釋是回傳最后一次toString的快取值,一旦StringBuffer被修改就清除這個快取值,看到這里有的小伙伴可能就有疑問了,為什么StringBuilder不使用這樣的一個欄位呢,參考了其它博客我了解到,StringBuffer是執行緒安全的,并且其方法都使用了synchronized關鍵字,性能與StringBuilder相比當然大打折扣,執行緒安全的應用場景是多執行緒,那么訪問的資料量肯定比單執行緒環境要大的多,所以說這個快取機制可以平衡StringBuffer的性能,
2.性能問題
??StringBuffer是執行緒安全的,它的所有公開方法都是同步的,而StringBuilder 是沒有對方法加鎖同步的,因此StringBuilder的性能要大于StringBuffer,
四、總結
1.String為固定長度的字串,StringBuilder和StringBuffer為變長字串;
2.stringBuffer是執行緒安全的,StringBuilder是非執行緒安全的;
3.StringBuffr和StringBuilder的默認初始容量是16,可以提前預估好字串的長度,進一步減少擴容帶來的額外開銷,
以上內容均為個人學習的一點心得,其中的知識點若有錯誤請留言提醒,若有侵權內容提醒馬上洗掉,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/152300.html
標籤:其他
