Java学習ノート12:JavaのStringBuilderクラス機能の詳細


文字列はJavaプログラムで最もよく使われるデータ構造の一つです.JavaのStringクラスに既にリロードされている"+".つまり、文字列は、次のコードに示すように「+」で直接接続できます.
String s = "abc" + "ddd"; 

でも本当にいいの?もちろん、この質問は簡単にYes or Noとは答えられません.具体的な状況によって決めなければならない.JavaではStringBuilderクラス(このクラスはJ 2 SE 5以上のバージョンでのみ提供され、以前のバージョンではStringBufferクラスが使用されていた)が提供されており、このクラスも「+」の役割を果たすことができます.では、私たちはどれを使うべきですか?
次に、次のコードを見てみましょう.
package string;

public class TestSimplePlus {

	public static void main(String[] args) {
		String s = "abc";
		String ss = "ok" + s + "xyz" + 5;
		System.out.println(ss);
	}

}

上のコードは正しい結果を出力します.表面的には文字列や整数型に「+」を使うのと変わらないが、実際にはそうなのだろうか.次に、このコードの本質を見てみましょう.
まず、JDKが持参したjavapやjadのような逆コンパイルツールを使用して、TestSimplePlusをJava Byte Codeに逆コンパイルします.その奥義は一目瞭然です.ここではjadを使用して逆コンパイルします.コマンドは次のとおりです.
jad -o -a -s d.java TestSimplePlus.class

逆コンパイルされたコードは次のとおりです.
package string;  
 
import java.io.PrintStream;  
 
public class TestSimplePlus {

	public TestSimplePlus() {
	//    0    0:aload_0
	//    1    1:invokespecial   #8   < Method void Object()>
	//    2    4:return
	}

	public static void main(String args[]) {
		String s = "abc";
		//    0    0:ldc1            #16  < String "abc">
		//    1    2:astore_1
		String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();
		//    2    3:new             #18  < Class StringBuilder>
		//    3    6:dup
		//    4    7:ldc1            #20  < String "ok">
		//    5    9:invokespecial   #22  < Method void StringBuilder(String)>
		//    6   12:aload_1
		//    7   13:invokevirtual   #25  < Method StringBuilder StringBuilder.append(String)>  
		//    8   16:ldc1            #29  < String "xyz">
		//    9   18:invokevirtual   #25  < Method StringBuilder StringBuilder.append(String)>  
		//   10   21:iconst_5
		//   11   22:invokevirtual   #31  < Method StringBuilder StringBuilder.append(int)>
		//   12   25:invokevirtual   #34  < Method String StringBuilder.toString()>
		//   13   28:astore_2
		System.out.println(ss);
		//   14   29:getstatic       #38  < Field PrintStream System.out>
		//   15   32:aload_2
		//   16   33:invokevirtual   #44  < Method void PrintStream.println(String)>
		//   17   36:return
	}
}

読者は上記のJavaバイトコードを見てぼんやりしているかもしれませんが、心配する必要はありません.本文の目的はJava Byte Codeを説明することではないので,具体的なバイトコードの意味を理解する必要はない.
jad逆コンパイルを使用する利点の1つは、バイトコードとソースコードを同時に生成できることです.これにより対照研究を行うことができる.上記のコードから、ソースプログラムでは「+」が使用されているが、コンパイル時には「+」をStringBuilderに変換していることがわかりやすい.したがって,Javaではどのような方法で文字列接続を行っても,実際にはStringBuilderクラスを用いていると結論づけることができる.
では、この結論に基づいて「+」とStringBuilderクラスを使う効果は同じではないでしょうか.これは二つの面から説明しなければならない.実行結果から説明すると、「+」とStringBuilderは完全に同等です.しかし、実行効率とリソース消費の面から見ると、それらには大きな違いがあります.
もちろん、文字のシリアル表現が簡単(上の順序構造のように)であれば、"+"とStringBuilderクラスは基本的に同じですが、文字列をループで接続するように複雑な構造であれば、Java Byte Codeには大きな違いがあります.まず、次のコードを見てみましょう.
package string;

import java.util.*;

public class TestComplexPlus {

	public static void main(String[] args) {
		String s = "";
		Random rand = new Random();
		for (int i = 0; i <  10; i++) {
			s = s + rand.nextInt(1000) + " ";
		}
		System.out.println(s);
	}

}

上のコードはコンパイルされたJava Byte Codeに戻ります.
package string;  
 
import java.io.PrintStream;  
import java.util.Random;  
 
public class TestComplexPlus {

	public TestComplexPlus() {
	//    0    0:aload_0
	//    1    1:invokespecial   #8   < Method void Object()>
	//    2    4:return
	}

	public static void main(String args[]) {
		String s = "";
		//    0    0:ldc1            #16  < String "">
		//    1    2:astore_1
		Random rand = new Random();
		//    2    3:new             #18  < Class Random>
		//    3    6:dup
		//    4    7:invokespecial   #20  < Method void Random()>
		//    5   10:astore_2
		for (int i = 0; i <  10; i++)
		//*   6   11:iconst_0
		//*   7   12:istore_3
		//*   8   13:goto            49
		s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append("").toString();
		//    9   16:new             #21  < Class StringBuilder>
		//   10   19:dup
		//   11   20:aload_1
		//   12   21:invokestatic    #23  < Method String String.valueOf(Object)>
		//   13   24:invokespecial   #29  < Method void StringBuilder(String)>
		//   14   27:aload_2
		//   15   28:sipush          1000
		//   16   31:invokevirtual   #32  < Method int Random.nextInt(int)>
		//   17   34:invokevirtual   #36  < Method StringBuilder StringBuilder.append(int)>
		//   18   37:ldc1            #40  < String " ">
		//   19   39:invokevirtual   #42  < Method StringBuilder StringBuilder.append(String)>
		//   20   42:invokevirtual   #45  < Method String StringBuilder.toString()>
		//   21   45:astore_1
		//   22   46:iinc            3  1  
		//   23   49:iload_3
		//   24   50:bipush          10
		//   25   52:icmplt          16
		System.out.println(s);
		//   26   55:getstatic       #49  < Field PrintStream System.out>
		//   27   58:aload_1
		//   28   59:invokevirtual   #55  < Method void PrintStream.println(String)>
		//   29   62:return
	}
}

コンパイラは「+」をStringBuilderクラスに変換しますが、StringBuilderオブジェクトを作成する場所はfor文の内部にあります.これは、ループを実行するたびにStringBuilderオブジェクトが作成されることを意味します(この例では、StringBuilderオブジェクトが10個作成されています).Javaにはゴミ回収器がありますが、この回収器の稼働時間は不定です.このようなごみが絶えず発生すれば、依然として大量の資源を占有する.この問題を解決する方法は、StringBuilderクラスをプログラムで直接使用して文字列を接続することです.コードは次のとおりです.
package string;  
 
import java.util.*;  
 
public class TestStringBuilder {

	public static void main(String[] args) {
		String s = "";
		Random rand = new Random();
		StringBuilder result = new StringBuilder();

		for (int i = 0; i <  10; i++) {
			result.append(rand.nextInt(1000));
			result.append(" ");
		}

		System.out.println(result.toString());
	}

}

上のコードを逆コンパイルした結果は次のとおりです.
package string;  
 
import java.io.PrintStream;  
import java.util.Random;  
 
public class TestStringBuilder {

	public TestStringBuilder() {
	//    0    0:aload_0
	//    1    1:invokespecial   #8   < Method void Object()>
	//    2    4:return
	}

	public static void main(String args[]) {
		String s = "";
		//    0    0:ldc1            #16  < String "">
		//    1    2:astore_1
		Random rand = new Random();
		//    2    3:new             #18  < Class Random>  
		//    3    6:dup
		//    4    7:invokespecial   #20  < Method void Random()>
		//    5   10:astore_2
		StringBuilder result = new StringBuilder();
		//    6   11:new             #21  < Class StringBuilder>
		//    7   14:dup
		//    8   15:invokespecial   #23  < Method void StringBuilder()>
		//    9   18:astore_3
		for (int i = 0; i <  10; i++)
		//*  10   19:iconst_0
		//*  11   20:istore          4
		//*  12   22:goto            47
		{
			result.append(rand.nextInt(1000));
		//   13   25:aload_3
		//   14   26:aload_2
		//   15   27:sipush          1000
		//   16   30:invokevirtual   #24  < Method int Random.nextInt(int)>
		//   17   33:invokevirtual   #28  < Method StringBuilder StringBuilder.append(int)>
		//   18   36:pop
			result.append(" ");
		//   19   37:aload_3
		//   20   38:ldc1            #32  < String " ">
		//   21   40:invokevirtual   #34  < Method StringBuilder StringBuilder.append(String)>
		//   22   43:pop
		}
		//   23   44:iinc            4  1
		//   24   47:iload           4
		//   25   49:bipush          10
		//   26   51:icmplt          25 
		System.out.println(result.toString());
		//   27   54:getstatic       #37  < Field PrintStream System.out>
		//   28   57:aload_3
		//   29   58:invokevirtual   #43  < Method String StringBuilder.toString()>
		//   30   61:invokevirtual   #47  < Method void PrintStream.println(String)>
		//   31   64:return
	}  
}

上記の逆コンパイルの結果から、StringBuilderを作成するコードはfor文の外に置かれていることがわかります.このような処理はソースプログラムでは複雑に見えますが、より効率的になり、消費されるリソースも少なくなります.
StringBuilderクラスを使用する場合は、「+」とStringBuilderを混在させないように注意してください.そうしないと、次のコードのようにStringBuilderオブジェクトがより多く作成されます.
for (int i = 0; i <  10; i++) {
	result.append(rand.nextInt(1000));
	result.append(" ");
}

次の形式に変更します.
for (int i = 0; i <  10; i++) {
	result.append(rand.nextInt(1000) + " ");
}

逆コンパイルの結果は次のとおりです.
for (int i = 0; i <  10; i++)
//*  10   19:iconst_0
//*  11   20:istore          4
//*  12   22:goto            65
{
	result.append((new StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString());
	//   13   25:aload_3
	//   14   26:new             #21  < Class StringBuilder>
	//   15   29:dup      

上記のコードから分かるように、Javaコンパイラは「+」をStringBuilderクラスにコンパイルし、for文がループごとにStringBuilderオブジェクトを作成します.
上記のコードをJDK 1.4でコンパイルする場合は、StringBuilderをStringBufferに変更する必要がありますが、JDK 1.4は「+」をStringBufferに変換します(JDK 1.4はStringBuilderクラスを提供していないため).StringBufferとStringBuilderの機能は基本的に同じですが、StringBufferはスレッドが安全ですが、StringBuilderはスレッドが安全ではありません.そのため、StringBuilderの効率が向上します.
転載先:
http://developer.51cto.com/art/200906/132698.htm