什麼情況下用+運算符進行字符串連接比調用StringBuffer/StringBuilder對象的append方法連接字符串性能更好?

原文鏈接:https://www.cnblogs.com/edhn/p/3289879.html

轉自:https://www.cnblogs.com/edhn/p/3289879.html

做String拼接時用StringBuilder(或StringBuffer)好還是直接用+號性能好?一般來說是前者,不過也有用加號略好的時候。
首先我一直認爲用+號有很好的可讀性,而且當String拼接在一個等式時,即形如
String s = “abc” + s2 + s3
jdk的實現原理也是轉換爲一個StringBuilder並一直append,效率也是差不太多的,所以我是比較喜歡在無循環或條件分支代碼的情況下全使用String相加,如果有循環或分支,就是寫成這樣:


StringBuilder hql =newStringBuilder(“select…………..”
+“  from”
+”  where”);
        If(xxxx){
          Hql.append(“xxxx”);
        }


 

 

不過大家的代碼一般都是全append方式,代碼一寫可以寫出幾百行,寫的時候不好寫,改的時候不好讀,真有點看不過去了。
今天特別對兩種拼接方式做了一下測試,來給大家一個參考。
找到項目中一個約200行的hql拼接,將append全轉化爲+號,如圖

 

測試代碼:

 

 


publicstaticvoid testStringJoin(){
        TestStringJoin instance =newTestStringJoin();
        longbegin, elapse;
        begin=System.currentTimeMillis();
        int execTimes =10000;
        for(int i =0; i < execTimes; i++){
            instance.testStringJoinWithPlus();
        }
        elapse =System.currentTimeMillis()-begin;
        System.out.println("testStringJoinWithPlus "+ execTimes
                +" times elapse = "+ elapse +"ms");

        begin=System.currentTimeMillis();
        for(int i =0; i < execTimes; i++){
            instance.testStringJoinWithStringBuilder();
        }
        elapse =System.currentTimeMillis()-begin;
        System.out.println("testStringJoinWithStringBuilder "+ execTimes
                +" times elapse = "+ elapse +"ms");
    }


 

結果:
testStringJoinWithPlus 10000 times elapse = 77ms
testStringJoinWithStringBuilder 10000 times elapse = 151ms (這裏有StringBuilder擴容問題,見下面繼續分析)

基本上可保持後者約爲前者2倍時間的情況,也就是說,用StringBuilder拼接字符串有時候還不如直接用+號拼接
爲啥呢?對比了一下兩方法的中間代碼,只取一小段就可以看出問題了
用StringBuilder拼接字符串的方式:

 

Code:
0:new#2; //class java/lang/StringBuilder
3: dup
4: sipush 5000
7: invokespecial #3; //Method java/lang/StringBuilder."<init>":(I)V
10: astore_1
11: aload_1
12: ldc #4; //String SELECT
14: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: pop
18: aload_1
19: ldc #6; //String new map(
21: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: pop
25: aload_1
26: ldc #7; //String corp.id AS corpId,
28: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: pop

用+號拼接的方式

 

Code:
0:new#2; //class java/lang/StringBuilder
3: dup
4: invokespecial #85; //Method java/lang/StringBuilder."<init>":()V
7: ldc #86; //String SELECT new map( corp.id AS corpId, ( SELECT COUNT(*) FROM TPerson per, TXXJL jl WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND jl.CSJYJ IS NULL AND( jl.NSpcz = ? OR jl.NSpcz = ? OR( jl.NSpcz = ? AND jl.NSpjg = ? ) OR( jl.NSpcz = ? AND jl.NSpjg = ? ) ) AND( jl.CJdyj IS NULL OR jl.CJdyj = ? ) AND jl.CBh NOT IN( SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id ) )AS dcs, ( SELECT COUNT(*) FROM TPerson per, TXXJL jl WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND jl.CJdyj IS NULL AND( jl.NSpcz = ? OR jl.NSpcz = ? OR( jl.NSpcz = ? AND jl.NSpjg = ? ) OR( jl.NSpcz = ? AND jl.NSpjg = ? ) ) AND( jl.CSJYJ IS NULL OR jl.CSJYJ = ? ) AND jl.CBh NOT IN( SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id ) )AS dyyjd, ( SELECT COUNT(*) FROM TPerson per, TXXJL jl WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND( jl.CSJYJ = ? AND jl.CJdyj = ? ) AND( jl.NSpcz = ? OR jl.NSpcz = ? OR( jl.NSpcz = ? AND jl.NSpjg = ? ) OR( jl.NSpcz = ? AND jl.NSpjg = ? ) ) AND jl.CBh NOT IN( SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id ) )AS dfpqc, ( SELECT COUNT(*) FROM TXXJL jl, TZf zfxx, TXXPctq pctq WHERE jl.CBhPerson = zfxx.CBh AND pctq.CBhJlxx = jl.CBh AND pctq.CBhPcxx = ? AND jl.NSpzt = ? AND jl.NSpcz != ? AND zfxx.corpId = corp.id )AS dspbw, ( SELECT COUNT(DISTINCT pt.CBhJlxx) FROM TXXPctq pt, TXXJL tq, TPerson per, TXXPcxx pc WHERE pc.NLx =? AND pc.CCjdw = corp.id AND pt.CBhJlxx = tq.CBh AND tq.CBhPerson = per.CBh AND pt.CBhPcxx = pc.CBh AND per.NSfyx =? AND per.NSfyx = 
9: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: getstatic #87; //Field SF_YES:Ljava/lang/Integer;
15: invokevirtual #88; //Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
18: ldc #66; //String AND tq.NTqlb IS NOT NULL
20: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #67; //String AND tq.NSpzt >= ?
25: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: ldc #68; //String AND tq.NSpzt <=?
30: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

後者說明,jdk在編譯期已經將用加號拼接的字符串理解爲一個字符串,不會再去創建一個StringBuilder一個一個的連接上去,而前者全程使用StringBuilder.append,就是實打實的拼接了,沒有給jdk一個優化的機會。
我測試使用的jdk版本是:jdk1.5.0_22
這種性能差異少有人提及,很多人討論過String相加的問題,直接得出儘量用StringBuilder(或StringBuffer)的結論,而沒有考慮到一直使用StringBuilder結構拼接字符串,除了不美觀不易讀外,還會有在性能上輸給 +號拼接 的問題
究其原因,大家一般測試時都使用很少量的字符串拼接,不太符合實際場景,沒有考慮到寫代碼時經常出現像上面的幾百行append帶來的影響。
當然,即便這樣兩者的性能差距仍然不大,而且也沒有在循環中用+號連接與append連接的差距大,但還有一點就是前面說的全程append的可讀性差得太多,用+號連接的可讀性顯然是更好的,難道不應該選擇更好的方式嗎?

 

還有一個好處

上面append代碼改爲全+號的那個截圖不清晰,實際我在+號連接字符串時加入了一個整數常量

 

+" AND per.NSfyx = "+TestStringJoin.SF_YES

因爲字符串+號拼接時如果不出現變量、非字符串常量的時候,編譯期就直接認爲是一個字符串了,同時因爲有字符串緩衝池的存在,
於是,這種情況下兩者的性能差異是:
testStringJoinWithPlus 10000 times elapse = 0ms
testStringJoinWithStringBuilder 10000 times elapse = 157ms

所以我非常推薦在遇到循環或條件分支的之前,寫sql就用+號拼接併合理換行排版,參數儘量都用?綁定,優雅又高效。

 

append中用+號拼接String如何?

有時候大家在用new StringBuilder拼接字符串時發現超出80列,就回車換行,IDE自動識別爲+號連接的字符串,如果忘了改就一直保持這樣了。
之前我跟別人說不推薦在append中使用+號連接,因爲這樣在append方法中可能又會引起一次new StrinBuilder,不過看來應該改改了。
經過上面的測試,上面的拼接字符串方法我寫成僅兩次append,可以預料到會有下面這個情況:

 

Code:
0:new#2; //class java/lang/StringBuilder
3: dup
4: invokespecial #85; //Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: aload_1
9: ldc #90; //String SELECT new map(corp.id AS corpId, (SELECT COUNT(*) FROM TPerson per, TXXJL jl WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND jl.CSJYJ IS NULL AND (jl.NSpcz = ? OR jl.NSpcz = ? OR (jl.NSpcz = ? AND jl.NSpjg = ?) OR (jl.NSpcz = ? AND jl.NSpjg = ?)) AND (jl.CJdyj IS NULL OR jl.CJdyj = ?) AND jl.CBh NOT IN (SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id)) AS dcs, (SELECT COUNT(*) FROM TPerson per, TXXJL jl 
11: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: pop
15: aload_1
16: ldc #91; //String WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND jl.CJdyj IS NULL AND (jl.NSpcz = ? OR jl.NSpcz = ? OR (jl.NSpcz = ? AND jl.NSpjg = ?) OR (jl.NSpcz = ? AND jl.NSpjg = ?)) AND (jl.CSJYJ IS NULL OR jl.CSJYJ = ?) AND jl.CBh NOT IN (SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id)) AS dyyjd, (SELECT COUNT(*) FROM TPerson per, TXXJL jl WHERE per.CBh = jl.CBhPerson AND per.corpId = corp.id AND (jl.CSJYJ = ? AND jl.CJdyj = ?) AND (jl.NSpcz = ? OR jl.NSpcz = ? OR (jl.NSpcz = ? AND jl.NSpjg = ?) OR (jl.NSpcz = ? AND jl.NSpjg = ?)) AND jl.CBh NOT IN (SELECT pctq.CBhJlxx FROM TXXPctq pctq, TXXPcxx pcxx WHERE pctq.CBhPcxx = pcxx.CBh AND pcxx.NLx = ? AND pcxx.CCjdw = corp.id)) AS dfpqc, (SELECT COUNT(*) FROM TXXJL jl, TZf zfxx, TXXPctq pctq WHERE jl.CBhPerson = zfxx.CBh AND pctq.CBhJlxx = jl.CBh AND pctq.CBhPcxx = ? AND jl.NSpzt = ? AND jl.NSpcz != ? AND zfxx.corpId = corp.id) AS dspbw, (SELECT COUNT(DISTINCT pt.CBhJlxx) FROM TXXPctq pt, TXXJL tq, TPerson per, TXXPcxx pc WHERE pc.NLx = ? AND pc.CCjdw = corp.id AND pt.CBhJlxx = tq.CBh AND tq.CBhPerson = per.CBh AND pt.CBhPcxx = pc.CBh AND per.NSfyx = ? AND per.NSfyx = 1 AND tq.NTqlb IS NOT NULL AND tq.NSpzt >= ? AND tq.NSpzt <= ? AND tq.NSpcz = ? AND tq.NSpjg = ?) AS tbcl, (SELECT COUNT(DISTINCT pt.CBhJlxx) FROM TXXPcxx pc, TXXPctq pt, TXXJL tq, TZf zf WHERE pc.NLx = ? AND pc.CCjdw = corp.id AND pt.CBhJlxx = tq.CBh AND tq.CBhPerson = per.CBh AND pt.CBhPcxx = pc.CBh AND per.NSfyx = ? AND tq.NTqlb IS NOT NULL AND ((tq.NSpzt >= ? AND tq.NSpzt <= ? AND tq.NSpcz = ? AND tq.NSpjg = ?) O (tq.NSpzt = ? AND tq.NSpcz >= ?))) AS djwzx) FROM XfzxCorp corp 
18: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: pop
22: aload_1
23: invokevirtual #84; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
26: areturn

編譯後發現沒有生成多餘的StringBuilder。
就是說,如果僅僅是因爲換行,而不是加入了其他的變量、常量、函數等情況下,append中的字符串可以出現+號拼接。
所以親們,把sql、hql其他啥啥字符串拼接寫得優雅點好不

 

最後測試一下StringBuilder擴容問題

用+號連接字符串,當需要創建StrinBuilder時,jdk使用了無參數的構造函數,相當於new StringBuilder(16)
在測試方法中,直接創建StrinBuilder時的代碼也是無參數的,這裏面雖然無差別,但細心的人應該會考慮一下這個問題。
+號連接方式,由於上面的結論很多字符串相加而中間沒有變量、非字符串常量等因素時,相當於一個字符串,也就是說至少需要14個外加進來的東西,纔會引起一次擴容;
而直接創建StrinBuilder時,有幾次append就算佔用了幾個容量,因此這也是後者效率差的一個原因。

測試的sql爲4789字節,使用new StringBuilder(5000)不會產生擴容問題,這種情況下看看頂樓兩種代碼的差距呢?
結果如下:
testStringJoinWithPlus 10000 times elapse = 66ms
testStringJoinWithStringBuilder 10000 times elapse = 85ms

後者性能好了不少,雖然數值相差不大,但按百分比來說效率提升約70%((151/85)-1),不過從理論上來說,不會超過+號連接字符串的情況

標籤: javaString

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