9

可能な場合、Javaコンパイラが「+」演算子で連結された文字列をStringBuilderのインスタンスにコンパイルする方法と、同じコードにコンパイルされるため、単純な「+」演算子を使用する方が良い方法について読んでいました。(while ループで文字列を作成する場合を除きます。その場合は、明らかに StringBuilder を使用するのが最適です。)

また、文字列に対する .concat メソッドは常に最悪の選択であることも読みました( Findbugsによってバグにされたほどです!)。

そこで、Eclipse で小さな Java クラスを作成して自分でテストすることにしました。私の結果は私を少し驚かせました。私が見つけたのは、コマンド ラインと比較して、Eclipse に準拠して実行した場合、さまざまな方法が比較的高速または低速であるということでした。

最初の日食の結果は次のとおりです。

the total millis to concatenate with + was: 12154
the total millis to concatenate with .concat was: 8840
the total millis to concatenate with StringBuilder was: 11350
the total millis to concatenate with StringBuilder with a specified size was: 5611

そのため、サイズが指定されたEclipse StringBuilderが最速で、次に.concat(奇妙な)が続き、StringBuilderと「+」連結はほとんど同じでした。

ただし、コマンドラインでの私の結果は次のとおりです。

the total millis to concatenate with + was: 4139
the total millis to concatenate with .concat was: 8590
the total millis to concatenate with StringBuilder was: 10888
the total millis to concatenate with StringBuilder with a specified size was: 6033

そのため、コマンドラインからコンパイルして実行すると、「+」演算子が明らかに最速で、次にサイズ付きの文字列ビルダー、次に連結、最後は通常の文字列ビルダーでした!

これは私には意味がありません。明らかに、私が読んだすべてのスタックオーバーフローの回答は、 + 演算子が通常の古い StringBuilder インスタンスにコンパイルされることは時代遅れでなければならないということです。

ここで実際に何が起こっているか知っている人はいますか?

私はjdk1.7.0_07を使用していますが、Eclipseとコマンドラインの両方がまったく同じものを参照していることがわかります。私が知っている唯一の違いは、Eclipseが「javaw」を使用していることですが、私が読んだことから、違いはありません。

私が何も悪いことをしていないことを確認したい場合は、ここに私のテストクラスがありますが、それはしっかりしていると確信しています。

public class Test {

    static final int LOOPS = 100000000;
    static final String FIRST_STRING = "This is such";
    static final String SECOND_STRING = " an awesomely cool ";
    static final String THIRD_STRING = "to write string.";

    /**
     * @param args
     */
    public static void main(String[] args) {

        Test.plusOperator();
        Test.dotConcat();
        Test.stringBuilder();
        Test.stringBuilderSizeSpecified();

    }

    public static void plusOperator() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            String toPrint = localOne + localTwo + localThree;
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with + was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void stringBuilder() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            StringBuilder toBuild = new StringBuilder()
                .append(localOne)
                .append(localTwo)
                .append(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with StringBuilder was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void stringBuilderSizeSpecified() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            StringBuilder toBuild = new StringBuilder(50)
                .append(localOne)
                .append(localTwo)
                .append(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with StringBuilder with a specified size was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

    public static void dotConcat() {
        String localOne = FIRST_STRING;
        String localTwo = SECOND_STRING;
        String localThree = THIRD_STRING;

        Calendar startTime = Calendar.getInstance();
        for (int x = 0; x < LOOPS; x++) {
            String toPrint = localOne.concat(localTwo).concat(localThree);
        }
        Calendar endTime = Calendar.getInstance();
        System.out.println("the total millis to concatenate with .concat was: " + 
                (endTime.getTimeInMillis() - startTime.getTimeInMillis()));
    }

}
4

2 に答える 2

12

StringBuilderOracle JDK 1.7 (javac 1.7.0_17) では、クラスで実行してバイトコードを取得することで示されるように、「+」演算子は引き続き を使用して実装されjavap -cます (ここではループのみを示します)。

public static void plusOperator();
Code:

  16: iload         4
  18: ldc           #10                 // int 100000000
  20: if_icmpge     53
  23: new           #11                 // class java/lang/StringBuilder
  26: dup           
  27: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
  30: aload_0       
  31: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  34: aload_1       
  35: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  38: aload_2       
  39: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  42: invokevirtual #14                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  45: astore        5
  47: iinc          4, 1
  50: goto          16


public static void stringBuilder();
Code:

  16: iload         4
  18: ldc           #10                 // int 100000000
  20: if_icmpge     50
  23: new           #11                 // class java/lang/StringBuilder
  26: dup           
  27: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
  30: aload_0       
  31: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  34: aload_1       
  35: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  38: aload_2       
  39: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  42: astore        5
  44: iinc          4, 1
  47: goto          16

これら 2 つの唯一の違いは、"+" を含むバージョンがループ内でStringBuilderを aに変換することです。String

したがって、問題は次のようになります:なぜあなたのテストは同じコードに対してそのような異なる結果を示すのですか? もっと完全に言えば、なぜこれは有効なマイクロベンチマークではないのでしょうか。考えられる理由は次のとおりです。

  • あなたは壁時計の時間を数えています。これは、テストの実行中に JVM が行っているすべてのことを実際に測定していることを意味します。これにはガベージ コレクションが含まれます (大量のガベージを作成しているため、これは重要です)。これは、スレッドの CPU 時間を取得することで軽減できます。
  • HotSpot がいつメソッドをコンパイルしているかを確認しません。これが、マイクロ ベンチマークの前にウォームアップ フェーズを実行する必要がある理由です。基本的にはmain()、実際のテストを実行する前に、複数回実行します。
于 2013-03-13T22:31:31.287 に答える
1

StringBuilder toBuild = new StringBuilder()ループの上に配置してみてください。同じことを文字列に対してString toPrint行う+=と、違いがわかります。
ループ内で新しい String および StringBuilder を作成しないでください。

于 2013-03-13T22:27:15.320 に答える