java四種字符串拼接方式性能分析

常見的四種字符串拼接方法分別是 
1.直接用“+”號 
2.使用String的方法concat 
3.使用StringBuilder的append 
4.使用StringBuffer的append

字符串拼接我分爲了兩種拼接方式,一種是同一個字符串大量循環拼接,第二種是大量不同的字符串每次都拼接固定數量的字符串。第二種更接近與服務器上的使用場景,因爲工程上是很少有一個字符串不斷拼接的,基本都是大量字符串按同一種模式拼接(比如構造上報參數)。代碼如下:

public class StringConcat {
    public static void main(String[] args) {
        plus();
        concat();
        stringBuffer();
        stringBuilder();
    }

    public static void plus(){
        String initial = "";
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial + "a";
        }
        long end = System.currentTimeMillis();
        System.out.println("plus:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            String a = "a";
            a = a + String.valueOf(i);
            //a = a + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i) + String.valueOf(i);
        }
        end = System.currentTimeMillis();
        System.out.println("double plus:" + (end - start));
    }

    public static void stringBuilder(){
        StringBuilder initial = new StringBuilder("");
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.append("b");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuilder:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            StringBuilder b = new StringBuilder("b");
            b.append(String.valueOf(i))
            //b.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double StringBuilder:" + (end - start));
    }

    public static void stringBuffer(){
        StringBuffer initial = new StringBuffer("");
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.append("c");
        }
        long end = System.currentTimeMillis();
        System.out.println("StringBuffer:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            StringBuffer c = new StringBuffer("c");
            c.append(String.valueOf(i));
            //c.append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i)).append(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double StringBuffer:" + (end - start));
    }

    public static void concat(){
        String initial = "";
        long start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            initial = initial.concat("d");
        }
        long end = System.currentTimeMillis();
        System.out.println("concat:" + (end - start));

        start = System.currentTimeMillis();
        for(int i = 0; i < 100000; i++){
            String d = "d";
            d = d.concat(String.valueOf(i));
            d = //d.concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i)).concat(String.valueOf(i));
        }
        end = System.currentTimeMillis();
        System.out.println("double concat:" + (end - start));
    }
}

結果如下: 

è¿éåå¾çæè¿°
結果:從結果可以看出,在拼接10萬次的情況下,循環拼接同一個字符串的時候,StringBuilder和StringBuffer的優勢是巨大的,僅僅花了不到10ms的時間,而StringBuilder稍快一點。而直接用“+”號和concat都花費了秒級的時間。但是在對10萬字符串都拼接一個串的時候,concat的情況發生了逆轉,速度甚至比StringBuilder和StringBuffer更快。

 

原理分析

1.加號拼接 
打開編譯後的字節碼我們可以發現加號拼接字符串jvm底層其實是調用StringBuilder來實現的,也就是說”a” + “b”等效於下面的代碼片。

String a = "a";
StringBuilder sb = new StringBuilder();
sb.append(a).append("b");
String str = sb.toString();


但並不是說直接用“+”號拼接就可以達到StringBuilder的效率了,因爲用“+”號每拼接一次都會新建一個StringBuilder對象,並且最後toString()方法還會生成一個String對象。在循環拼接十萬次的時候,就會生成十萬個StringBuilder對象,十萬個String對象,這簡直就是噩夢。

2.concat拼接 
concat的源代碼如下,可以看到,concat其實就是申請一個char類型的buf數組,將需要拼接的字符串都放在這個數組裏,最後再轉換成String對象。

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

3.StringBuilder/StringBuffer 
這兩個類實現append的方法都是調用父類AbstractStringBuilder的append方法,只不過StringBuffer是的append方法加了sychronized關鍵字,因此是線程安全的。append代碼如下,他主要也是利用char數組保存字符,通過ensureCapacityInternal方法來保證數組容量可用還有擴容。

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

他擴容的方法的代碼如下,可見,當容量不夠的時候,數組容量右移1位(也就是翻倍)再加2,以前的jdk貌似是直接寫成int newCapacity = (value.length * 2) + 2,後來優化成了右移,可見,右移的效率還是比直接乘更高的。

private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity;
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
            ? hugeCapacity(minCapacity)
            : newCapacity;
    }

 

原因分析

1.循環拼接字符串 
通過實驗我們發現,在循環拼接同一個字符串的時候,他們效率的按快慢排序是 
StringBulider > StringBuffer >> String.concat > “+”。 
StringBulider比StringBuffer更快這個容易理解,因爲StringBuffer的方法是sychronized修飾的,同步的時候會損耗掉一些性能。StringBulider和String.concat的區別,主要在擴容上,String.concat是需要多少擴多少,而StringBulider是每次翻兩倍,指數級擴容。在10萬次拼接中,String.concat需要擴容10萬次,而StringBuilder只需要擴容log100000次(大約17次),除此之外,concat每次都會生成一個新的String對象,而StringBuilder則不必,那StringBuilder如此快就不難解釋了。至於直接用“+”號連接,之前已經說了,它會產生大量StringBuilder和String對象,當然就最慢了。 
2.大量字符串拼接 
在只拼接少量字符串的情況下的時候,他們效率的按快慢排序是 
String.concat > StringBulider > StringBuffer > “+”。 
爲什麼在拼接少量字符串的時候,String.concat就比StringBuilder快了呢,原因大致有兩點,一是StringBuilder的調用棧更深,二是StringBuilder產生的垃圾對象更多,並且它重寫的toString方法爲了做到不可變性採用了“保護性拷貝”,因此效率不如concat。 
詳細原因分析參考:concat和StringBuilder性能分析 
保護性拷貝見:保護性拷貝 
當拼接的字符串少的時候,concat因爲上述優勢略快,但是當一次性拼接字符串多的時候,StringBuilder的擴容更少優勢便會開始展現出來,例如一次拼接8個字符串,concat的效率就已經明顯不如StringBuilder了,如下圖。 

ä¸æ¬¡æ¼æ¥8个å­ç¬¦ä¸²


結論

從以上分析我們可以得出以下幾點結論 
1.無論如何直接用“+”號連接字符串都是最慢的 
2.在拼接少數字符串(不超過4個)的時候,concat效率是最高的 
3.多個字符串拼接的時候,StringBuilder/StringBuffer的效率是碾壓的 
4.在不需要考慮線程安全問題的時候,使用StringBuilder的效率比StringBuffer更高

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章