String拼接也有用加號更好的時候

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


StringBuilder hql =newStringBuilder(「select…………..」
+  from
+」  where」);
        If(xxxx){
          Hql.append(「xxxx」);
        }web

 

 

不過你們的代碼通常都是全append方式,代碼一寫能夠寫出幾百行,寫的時候很差寫,改的時候很差讀,真有點看不過去了。
今天特別對兩種拼接方式作了一下測試,來給你們一個參考。
找到項目中一個約200行的hql拼接,將append全轉化爲+號,如圖sql

 
測試代碼:
 

 


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");
    }app

 

結果:
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_YESui

由於字符串+號拼接時若是不出現變量、非字符串常量的時候,編譯期就直接認爲是一個字符串了,同時由於有字符串緩衝池的存在,
因而,這種狀況下二者的性能差別是:
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: areturnspa

編譯後發現沒有生成多餘的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),不過從理論上來講,不會超過+號鏈接字符串的狀況
相關文章
相關標籤/搜索