web-dev-qa-db-ja.com

StringBuilderはC#の内部でどのように機能しますか?

StringBuilderはどのように機能しますか?

それは何をしますか内部的に?安全でないコードを使用していますか?そして、なぜそれがとても速いのですか(+演算子と比較して)?

46
Alon Gubkin

+演算子を使用して文字列を作成する場合:

string s = "01";
s += "02";
s += "03";
s += "04";

次に、最初の連結で、長さ4の新しい文字列を作成し、それに「01」と「02」をコピーします。4文字がコピーされます。 2番目の連結では、長さ6の新しい文字列を作成し、それに「0102」と「03」をコピーします。6文字がコピーされます。 3番目の連結では、長さ8の文字列を作成し、それに「010203」と「04」をコピーします。8文字がコピーされます。これまでに、この8文字の文字列に対して合計4 + 6 + 8 = 18文字がコピーされました。立ち止まるな。

...
s += "99";

98番目の連結で、長さ198の文字列を作成し、それに「010203 ... 98」と「99」をコピーします。これにより、この198文字の文字列を作成するために、合計4 + 6 + 8 + ... + 198 =ロットが得られます。

文字列ビルダーは、そのすべてのコピーを行うわけではありません。むしろ、最終的な文字列よりも大きくなることが期待される可変配列を維持し、必要に応じて新しいものを配列に詰め込みます。

推測が間違っていて、配列がいっぱいになるとどうなりますか? 2つの戦略があります。以前のバージョンのフレームワークでは、文字列ビルダーは、配列がいっぱいになったときに配列を再割り当てしてコピーし、サイズを2倍にしました。新しい実装では、文字列ビルダーは比較的小さな配列のリンクリストを維持し、古い配列がいっぱいになるとリストの最後に新しい配列を追加します。

また、ご想像のとおり、文字列ビルダーは「安全でない」コードを使ってトリックを実行し、パフォーマンスを向上させることができます。たとえば、新しいデータを配列に書き込むコードは、配列の書き込みが範囲内にあることをすでに確認している可能性があります。安全システムをオフにすることで、アレイへのすべての書き込みが安全であることを確認するために、ジッタが挿入される可能性のある書き込みごとのチェックを回避できます。文字列ビルダーは、バッファーが再割り当てされるのではなく再利用されるようにする、不要な安全性チェックを回避するなどのことを行うために、これらの種類のトリックをいくつか実行します。安全でないコードを正しく書くのが本当に上手で、パフォーマンスの最後のビットをすべて引き出す必要が本当にない限り、私はこれらの種類のシェナニガンに対してお勧めします。

68
Eric Lippert

StringBuilderの実装はバージョン間で変更されていると思います。基本的には、それは何らかの形の可変構造を維持します。 usedは、(内部メソッドを使用して)まだ変更されている文字列を使用し、返された後に変更されないようにするだけだと思います。

StringBuilderがループで文字列連結を使用するよりも速い理由は、まさに可変性のためです-各突然変異の後に新しい文字列を構築する必要はありません、これは文字列内のすべてのデータをコピーすることを意味します。

単一の連結の場合、実際にはStringBuilderを使用するよりも+を使用する方がわずかに効率的です。 複数の操作を実行している場合にのみ、StringBuilderが光る中間結果は実際には必要ありません。

詳細については、 StringBuilder に関する私の記事を参照してください。

17
Jon Skeet

Microsoft CLRは、内部呼び出しを使用して一部の操作を実行します(安全でないコードとはまったく同じではありません)。一連の+連結文字列に対する最大のパフォーマンス上の利点は、char[]に書き込み、それほど多くの中間文字列を作成しないことです。 ToString()を呼び出すと、コンテンツから完成した不変の文字列が作成されます。

3
agent-j

StringBuilderは、変更できない通常のStringと比較して、変更可能な文字列バッファを使用します。 ToStringStringBuilderメソッドを呼び出すと、文字列バッファがフリーズして通常の文字列に変換されるだけなので、すべてのデータをもう一度コピーする必要はありません。

StringBuilderは文字列バッファを変更できるため、文字列データを変更するたびに新しい文字列値を作成する必要はありません。 +演算子を使用すると、コンパイラはそれをString.Concat呼び出しに変換し、新しい文字列オブジェクトを作成します。この一見無害なコードの一部:

str += ",";

これにコンパイルされます:

str = String.Concat(str, ",");
1
Guffa