StringBuffer
Java で文字列を連結するために使用する方が、s に+
演算子を使用するよりも効率的であると誰かが私に言いましたString
。あなたがそれをすると、ボンネットの下で何が起こりますか? 何がStringBuffer
違うのですか?
19 に答える
最近では、ほとんどすべての場合、StringBuilder(非同期バージョンです。いつ文字列を並列に構築しますか?)を使用することをお勧めしますが、次のようになります。
+を2つの文字列で使用すると、次のようなコードがコンパイルされます。
String third = first + second;
このようなものに:
StringBuilder builder = new StringBuilder( first );
builder.append( second );
third = builder.toString();
したがって、ほんの少しの例では、通常、違いはありません。しかし、複雑な文字列を作成する場合、多くの場合、これよりも多くの処理が必要になります。たとえば、さまざまな追加ステートメントを使用している場合や、次のようなループを使用している場合があります。
for( String str : strings ) {
out += str;
}
この場合、各反復で新しいStringBuilder
インスタンスと新しい( --sString
の新しい値は不変です)が必要です。これは非常に無駄です。これをシングルに置き換えると、シングルを作成するだけで、ヒープを気にしないもので埋めることができなくなります。out
String
StringBuilder
String
String
次のような単純な連結の場合:
String s = "a" + "b" + "c";
使用するのはかなり無意味ですStringBuffer
-ジョドネルが指摘したように、スマートに翻訳されます:
String s = new StringBuffer().append("a").append("b").append("c").toString();
ただし、次のように、ループ内で文字列を連結するのは非常に効率的ではありません。
String s = "";
for (int i = 0; i < 10; i++) {
s = s + Integer.toString(i);
}
このループで string を使用すると、メモリ内に "0"、"01"、"012" などの 10 個の中間文字列オブジェクトが生成されます。を使用して同じことを書いている間、StringBuffer
単にいくつかの内部バッファを更新StringBuffer
し、必要のない中間文字列オブジェクトを作成しません:
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++) {
sb.append(i);
}
実際には、上記の例では、StringBuilder
(Java 1.5 で導入された) の代わりに使用する必要がありますStringBuffer
-StringBuffer
は、すべてのメソッドが同期されるため、少し重くなります。
一方が他方より速くなるべきではありません。Java 1.4.2 より前のバージョンでは、"+" 演算子を使用して 3 つ以上の文字列を連結するString
と、最終的な文字列を構築するプロセス中に中間オブジェクトが作成されるため、これは当てはまりませんでした。
ただし、StringBuffer の JavaDoc に記載されているように、少なくとも Java 1.4.2 以降、「+」演算子を使用してコンパイルすると、多くの文字列が作成され、そこStringBuffer
にappend()
ing されます。どうやら違いはありません。
ただし、ループ内で文字列を別の文字列に追加する場合は注意してください。例えば:
String myString = "";
for (String s : listOfStrings) {
// Be careful! You're creating one intermediate String object
// for every iteration on the list (this is costly!)
myString += s;
}
ただし、通常、いくつかの文字列を「+」で連結する方が、append()
すべてを連結するよりもクリーンであることに注意してください。
内部では、実際に StringBuffer を作成して追加し、結果に対して toString() を呼び出します。したがって、実際にはどちらを使用しても問題ありません。
そう
String s = "a" + "b" + "c";
になる
String s = new StringBuffer().append("a").append("b").append("c").toString();
これは、単一のステートメント内の一連のインライン追加に当てはまります。複数のステートメントで文字列を作成すると、メモリが無駄になるため、StringBuffer または StringBuilder を選択することをお勧めします。
jdk1.5(またはそれ以上)があり、連結がスレッドセーフである場合、StringBufferの代わりにStringBuilderを使用する必要があると思います http://java4ever.blogspot.com/2007/03/string-vs-stringbuffer-vs-stringbuilder.html 速度の向上について: http ://www.about280.com/stringtest.html
個人的には読みやすさのためにコードを作成するので、文字列の連結によってコードがかなり遅くなることがわかった場合を除いて、コードを読みやすくする方法を使用してください。
場合によっては、コンパイラによって実行される最適化のためにこれが廃止されますが、一般的な問題は次のようなコードです。
string myString="";
for(int i=0;i<x;i++)
{
myString += "x";
}
以下のように動作します (各ステップは次のループ反復です)。
- 長さ 1 の文字列オブジェクトを構築し、値は "x"
- サイズ 2 の新しい文字列オブジェクトを作成し、古い文字列 "x" をコピーして、位置 2 に "x" を追加します。
- サイズ 3 の新しい文字列オブジェクトを作成し、古い文字列 "xx" をコピーして、位置 3 に "x" を追加します。
- ... 等々
ご覧のとおり、反復ごとにもう 1 文字コピーする必要があるため、ループごとに 1+2+3+4+5+...+N 回の操作を実行することになります。これは O(n^2) 操作です。ただし、必要な文字数が N 文字だけであることが事前にわかっていれば、使用していた文字列から N 文字だけをコピーして、1 回の割り当てでそれを行うことができます - 単なる O(n) 操作です。
StringBuffer/StringBuilder は変更可能であるため、これを回避します。同じデータを何度もコピーし続ける必要はありません (内部バッファーにコピーするスペースがある限り)。それらは、現在のサイズの割合でバッファを過剰に割り当てることによって行われる追加の数に比例する割り当てとコピーを実行することを避け、償却された O(1) の追加を与えます。
ただし、多くの場合、コンパイラはコードを StringBuilder スタイルに最適化できることに注意してください (または、定数の折りたたみなどを実行できるため)。
Javaは、string1 + string2をStringBuffer構造、append()、およびtoString()に変換します。意味あり。
ただし、Java 1.4以前では、ステートメント内の+演算子ごとに個別にこれを行います。これは、a + b + cを実行すると、2つのtoString()呼び出しを持つ2つのStringBufferコンストラクトが生成されることを意味します。あなたがコンキャットの長い文字列を持っていた場合、それは本当の混乱に変わるでしょう。自分でそれを行うことは、これを制御して適切に行うことができることを意味しました。
Java 5.0以降はそれをより賢明に行うように思われるので、問題は少なく、確かに冗長性は低くなります。
私の知る限り、JVMのバージョンに依存します。1.5より前のバージョンでは、「+」または「+ =」を使用して実際に毎回文字列全体をコピーしていました。
+= を使用すると、実際には文字列の新しいコピーが割り当てられることに注意してください。
ループで + を使用すると指摘されたように、コピーが含まれます。
連結された文字列がコンパイル時に連結されたコンパイル時定数である場合、
String foo = "a" + "b" + "c";
Has は次のようにコンパイルされます。
String foo = "abc";
StringBufferクラスは、連結する文字列の内容を保持するために文字の配列を維持しますが、+メソッドは、呼び出されるたびに新しい文字列を作成し、2つのパラメーター(param1 + param2)を追加します。
StringBufferは、1。既存の配列を使用して、すべての文字列を連結/格納できる可能性があるため、より高速です。2.配列に収まらない場合でも、より大きなバッキング配列を割り当ててから、呼び出しごとに新しいStringオブジェクトを生成する方が高速です。
さらに詳しい情報:
StringBuffer はスレッドセーフなクラスです
public final class StringBuffer extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public synchronized StringBuffer append(StringBuffer stringbuffer)
{
super.append(stringbuffer);
return this;
}
// .. skip ..
}
ただし、StringBuilder はスレッドセーフではないため、可能な場合は StringBuilder を使用する方が高速です
public final class StringBuilder extends AbstractStringBuilder
implements Serializable, CharSequence
{
// .. skip ..
public StringBuilder append(String s)
{
super.append(s);
return this;
}
// .. skip ..
}
'+'を使用して2つの文字列を連結するには、両方の文字列にスペースを使用して新しい文字列を割り当ててから、両方の文字列からデータをコピーする必要があります。StringBufferは連結用に最適化されており、最初に必要なスペースよりも多くのスペースを割り当てます。新しい文字列を連結する場合、ほとんどの場合、文字は既存の文字列バッファの最後に簡単にコピーできます。
2つの文字列を連結する場合、「+」演算子のオーバーヘッドはおそらく少なくなりますが、より多くの文字列を連結すると、StringBufferが優先され、使用するメモリ割り当てが少なくなり、データのコピーが少なくなります。
文字列は不変であるため、+演算子を呼び出すたびに、新しい文字列オブジェクトが作成され、文字列データが新しい文字列にコピーされます。文字列のコピーには文字列の長さに比例した時間がかかるため、+演算子をN回呼び出すと、実行時間はO(N 2)になります(2次式)。
逆に、StringBufferは可変であるため、Append()を実行するたびに文字列をコピーする必要はありません。したがって、N個のAppend()呼び出しのシーケンスにはO(N)時間がかかります(線形)。これは、多数の文字列を一緒に追加する場合にのみ、実行時に大きな違いをもたらします。
StringBuffer は可変です。別のオブジェクトをインスタンス化せずに、文字列の値を同じオブジェクトに追加します。次のようなことをします:
myString = myString + "XYZ"
新しいString オブジェクトを作成します。
前述のように、String オブジェクトは不変です。つまり、いったん作成されると (以下を参照)、変更することはできません。
String x = new String("something"); // また
文字列 x = "何か";
したがって、String オブジェクトを連結しようとすると、それらのオブジェクトの値が取得され、新しい String オブジェクトに入れられます。
代わりに変更可能な StringBuffer を使用する場合は、char (プリミティブ) の内部リストに値を継続的に追加します。このリストは、必要な値に合わせて拡張または切り詰めることができます。新しいオブジェクトは作成されず、値を保持するために必要なときに新しい文字のみが作成/削除されます。
2 つの文字列を連結すると、実際には Java で 3 番目の String オブジェクトが作成されます。StringBuffer (または Java 5/6 の StringBuilder) を使用すると、文字列の内部配列を使用して文字列を格納し、その add(...) メソッドの 1 つを使用すると、新しい文字列が作成されないため、高速になります。物体。代わりに、StringBuffer/Buider は内部配列を追加します。
単純な連結では、StringBuffer/Builder を使用して文字列を連結するか、「+」演算子を使用して文字列を連結するかは実際には問題ではありませんが、多くの文字列連結を行う場合は、StringBuffer/Builder を使用する方がはるかに高速であることがわかります。
その理由は、文字列が不変であることです。文字列を変更する代わりに、新しい文字列を作成します。文字列プールは、ガベージ コレクターがそれをプラッシュするまで、すべての文字列値を格納します。とのように 2 つの文字列があるHello
としhow are you
ます。String プールを考えると、2 つの String があります。
これら 2 つの文字列を次のように連結しようとすると、
文字列 1 = 文字列 1 + 文字列 2
ここで、新しい String オブジェクトを作成し、String プールに格納します。
何千もの単語を連結しようとすると、メモリが増えます。これに対する解決策は、StringBuilder または StringBuffer です。オブジェクトは 1 つしか作成できず、変更できます。どちらも可変であるため、メモリを増やす必要はありません。スレッドセーフと考える場合は StringBuffer を使用し、それ以外の場合は StringBuilder を使用します。
public class StringExample {
public static void main(String args[]) {
String arr[] = {"private", "default", "protected", "public"};
StringBuilder sb= new StringBuilder();
for (String value : arr) {
sb.append(value).append(" ");
}
System.out.println(sb);
}
}
出力: プライベート デフォルト 保護 パブリック
最も簡単な答えは、より速いということだと思います。
本当にすべての内部情報を知りたい場合は、いつでも自分でソースを確認できます。
Java では文字列は不変であるため、文字列を連結するたびに新しいオブジェクトがメモリ内に作成されます。SpringBuffer はメモリ内の同じオブジェクトを使用します。
Java 言語仕様のString Concatenation Operator +のセクションでは、+ 演算子が非常に遅くなる理由についての背景情報を提供しています。