StringBuiderで文字列を置換すると"+"の性能が向上しますか?

5164 ワード

回転:http://www.ticmy.com/?p=69
Javaコードの最適化を語る際に、文字列の接続操作を「+」から「StringBuider」に切り替えるというフォーラムをよく見ますが、後は簡単のために、SteringBuilderというだけです.
忙しくない答えはいくつかの例を見てください.コードは以下の通りです.
public class StringConcat {
    public static void main(String... args) {
        concat1();
        concat2();
        concat3();
    }
 
    public static void concat1() {
        String s = "today is " + "a good day";
        System.out.println(s);
    }
 
    public static void concat2() {
        int count = 2;
        String tmp = " on the desk";
        String s2 = "there are " + count + " books " + tmp;
        System.out.println(s2);
    }
 
    public static void concat3() {
        String s3 = "";
        for(int i=0; i<100; i++) {
            s3 = s3 + i;
        }
        System.out.println(s3);
    }
}
次に、これらの3つの操作文字列の方法を分析し、javap命令によって逆コンパイル.classファイル:javap-c StringContictを行い、バイトコード命令は以下の通りである(concat 1,concat 2,concat 3の3つの方法だけを摘出する).
public static void concat1();
  Code:
   0:   ldc     #5; //String today is a good day
   2:   astore_0
   3:   getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   6:   aload_0
   7:   invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   10:  return
 
public static void concat2();
  Code:
   0:   iconst_2
   1:   istore_0
   2:   ldc     #8; //String  on the desk
   4:   astore_1
   5:   new     #9; //class java/lang/StringBuilder
   8:   dup
   9:   invokespecial   #10; //Method java/lang/StringBuilder."":()V
   12:  ldc     #11; //String there are
   14:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   17:  iload_0
   18:  invokevirtual   #13; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   21:  ldc     #14; //String  books
   23:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   26:  aload_1
   27:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   30:  invokevirtual   #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   33:  astore_2
   34:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   37:  aload_2
   38:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   41:  return
 
public static void concat3();
  Code:
   0:   ldc     #16; //String
   2:   astore_0
   3:   iconst_0
   4:   istore_1
   5:   iload_1
   6:   bipush  100
   8:   if_icmpge       36
   11:  new     #9; //class java/lang/StringBuilder
   14:  dup
   15:  invokespecial   #10; //Method java/lang/StringBuilder."":()V
   18:  aload_0
   19:  invokevirtual   #12; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   22:  iload_1
   23:  invokevirtual   #13; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   26:  invokevirtual   #15; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   29:  astore_0
   30:  iinc    1, 1
   33:  goto    5
   36:  getstatic       #6; //Field java/lang/System.out:Ljava/io/PrintStream;
   39:  aload_0
   40:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
を選択して、次の3つの方法のバイトコードの意味を分析します.
concat 1では、2つの文字の面の値(文字列の定数)を接続し、concat 1のバイトコードの0番目(0:ldc钾5;/String today is a good day)から見られます.この方法は直接常量池からロードされます.この文はコンパイル後に文字列になり、String=「String today is a good day」に相当するもので、動作中は接続操作をする必要は全くないので、文字列の面の接続にはString Buiderを使う意味がない.
concat 2では、変数参加文字列の接続です.逆コンパイルのバイトコードから分かるように、コンパイル期間はSteringBuilderのapped操作に変換されています.
String s2 = "there are " + count + " books " + tmp;
ステートメントはコンパイルされた後(すなわち[5,30]間の命令に相当します):
String s2 = new StringBuilder().append("there are ").append(count).append(" books").append(tmp).toString();
このように、このような文字列接続コードにnew StringBuider()を明示的に使用することは、Steringの「+」オペレータがコンパイル時にnew StringBuider()・appedに変換されているため、性能の向上にはつながりません.もちろん、StringBuiderは、その初期容量としてのintパラメータを導入することができますが、これは生成されたコードではできません.プログラムだけで制御できます.
最後にconcat 3を見て、forサイクルで文字列を使って接続します.最後にfor循環外で接続後の文字列を使います.バイトコードの[11,29]の間は循環体であり、循環体の中でnew StringBuiderの動作が行われていることが分かりやすく、バイトコードの代表的なコードの意味は以下の通りである.
String s3 = "";
for(int i=0; i<100; i++) {
    s3 = new StringBuilder().append(s3).append(i).toString();
}
この場合、コンパイラの最適化は私達の意に及ばないです.
私たちが欲しい最適化コードはこうです.
String s3 = "";
StringBuilder tmp = new StringBuilder();
tmp.append(s3);
for(int i=0; i<100; i++) {
    tmp.append(i);
}
s3 = tmp.toString();
これはコンパイラにとってちょっと複雑です.手作業でできます.
以上の3つの方法の分析により、StringBuider(StringBuffer)で文字列を置換することができますか?コンパイラは、明示的に使用できる場合以外は、有効な初期容量を与えることができます.このような意味では、個人的にはstringの「+」はStringBuiderの文法飴と考えられています.しかし、もしconcat 3のような循環中の文字列接続をするなら、我々は明示的にStringBuiderを使用する必要があります.jdk 1.4では、まだStringBuiderクラスがありません.コンパイラで生成された最適化コードはStringBufferを使用します.
String接続操作コンパイラに対して生成されたString Budler appedは、必ず単一スレッドで動作しますので、スレッドの安全問題はありません.