假設有一個字串,我們將對這個字串做大量循環拼接操作,使用」+」的話將得到最低的效能。但是究竟這個效能有多差?如果我們同時也把StringBuffer,StringBuilder或String.concat()放入效能測試中,結果又會如何呢?本文將會就這些問題給出一個答案!
我們將使用Per4j來計算效能,因為這個工具可以給我們一個完整的效能指標集合,例如最小,最大耗時,統計時間段的標準差等。在測試程式碼中,為了得到一個準確的標準差值,我們將執行20個拼接”*”50,000次的測試。以下是我們將使用到的拼接字串的方法:
複製代碼代碼如下:
Concatenation Operator (+)
String concat method concat(String str)
StringBuffer append method append(String str)
StringBuilder append method append(String str)
最後,我們將來看看字節碼,來研究這些方法到底是如何執行的。現在,讓我們先開始來創建我捫的類別。注意為了計算每個循環的效能,程式碼中的每段測試程式碼都需要用Per4J函式庫進行封裝。首先我們先定義迭代次數
複製代碼代碼如下:
private static final int OUTER_ITERATION=20;
private static final int INNER_ITERATION=50000;
接下來,我們將使用上述4個方法來實作我們的測試程式碼。
複製代碼代碼如下:
String addTestStr = "";
String concatTestStr = "";
StringBuffer concatTestSb = null;
StringBuilder concatTestSbu = null;
for (int outerIndex=0;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch("StringAddConcat");
addTestStr = "";
for (int innerIndex=0;innerIndex<=INNER_ITERATION;innerIndex++)
addTestStr += "*";
stopWatch.stop();
}
for (int outerIndex=0;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch("StringConcat");
concatTestStr = "";
for (int innerIndex=0;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestStr.concat("*");
stopWatch.stop();
}
for (int outerIndex=0;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch("StringBufferConcat");
concatTestSb = new StringBuffer();
for (int innerIndex=0;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestSb.append("*");
stopWatch.stop();
}
for (int outerIndex=0;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch("StringBuilderConcat");
concatTestSbu = new StringBuilder();
for (int innerIndex=0;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestSbu.append("*");
stopWatch.stop();
}
接下來透過運行程式來產生效能指標。我的運行環境是64位元的Windown7作業系統,32位元的JVM(7-ea) 帶4GB內存,雙核心Quad 2.00GHz的CPU的機器
結果非常完美如我們想像的那樣。唯一比較有趣的是為什麼String.concat也很不錯,我們都知道,String是一個常數類別(初始化後就不會改變的類別),那麼為什麼concat的效能會更好一些呢。 (譯者註: 其實原文作者的測試程式碼有問題,對於concat()方法的測試程式碼應該寫成concatTestStr=concatTestStr.concat(“*”)才對。)為了回答這個問題,我們應該看看concat反編譯出來的字節碼。在本文的下載包裡包含了所有的字節碼,但現在我們先來看看concat的這個程式碼片段:
複製代碼代碼如下:
46: new #6; //class java/lang/StringBuilder
49: dup
50: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V
53: aload_1
54: invokevirtual #8; //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
57: ldc #9; //String *
59: invokevirtual #8; //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
62: invokevirtual #10; //Method java/lang/StringBuilder.toString:()
Ljava/lang/String;
65: astore_1
66: iinc 7, 1
69: goto 38
這段程式碼是String.concat()的字節碼,從這段程式碼中,我們可以清楚的看到,concat()方法使用了StringBuilder,concat()的效能應該和StringBuilder的一樣好,但是由於額外的建立StringBuilder和做.append(str).append(str).toString()的操作,使得concate的效能會受到一些影響,所以StringBuilder和String Cancate的時間是1.8和3.3。
因此,即時在做最簡單的拼接時,如果我們不想建立StringBuffer或StringBuilder實例使,我們也因該使用concat。但是對於大量的字串拼接操作,我們就不應該使用concat(譯者註:因為測試代碼功能上並不完全等價,更換後的測試代碼concat的平均處理時間是1650.9毫秒。這個結果在原文的評論裡面。因此,在不考慮執行緒安全性和同步的情況下,為了獲得最高的效能,我們應盡量使用StringBuilder。