StringBuilderクラス

文字列の高速な編集

文字列(string型)同士の結合は+演算子やstring.Concatメソッドを使用します。
通常はこれで十分ですが、C#ではstring型のデータは変更ができないという特徴があります。
つまり単純な文字列結合であっても新しい文字列オブジェクトを生成しているため、その分のコストがやや掛かります。

通常は問題になるようなコストではありませんが、同じ変数に対して何度も文字列結合を繰り返して文字列を構築する場合はStringBuilderクラスを使用したほうが高速になる可能性があります。
(System.Text名前空間)
StringBuilderクラスは文字列の結合時に新しいオブジェクトは生成せず、既存のオブジェクトに直接結合していきます。


string s = "a";

//時間計測のためのクラス
var sw = new System.Diagnostics.Stopwatch();

//+演算子による結合
{
    string str1 = "";

    //計測スタート
    sw.Start();

    //10万回繰り返し
    for (int i = 0; i < 100000; i++)
    {
        str1 += s;
    }

    //計測ストップ
    sw.Stop();

    //経過時間を表示
    Console.WriteLine(sw.Elapsed);
}

//経過時間をリセット
sw.Reset();

//StringBuilderによる結合
{
    StringBuilder sb = new StringBuilder();

    //計測スタート
    sw.Start();

    //10万回繰り返し
    for (int i = 0; i < 100000; i++)
    {
        sb.Append(s);
    }

    //StringBuilderをstring型に変換
    string str2 = sb.ToString();

    //計測ストップ
    sw.Stop();

    //経過時間を表示
    Console.WriteLine(sw.Elapsed);
}
00:00:00.9545671
00:00:00.0005509

実行結果は環境によって異なります。

System.Diagnostics.Stopwatchクラスは時間計測のためのクラスです。
これを利用して文字列結合に掛かった時間を計測します。
使い方はサンプルコード中のコメントを参照してください。

まずStringBuilderクラスのインスタンスをnewで生成します。
このインスタンスに対して文字列操作を行います。

文字列の結合はAppendメソッドで行います。
引数はstring型以外にも組み込み型(int型やdouble型など)も指定できます。
(その他のクラスはToStringメソッドが呼び出されます)

StringBuilderクラスのインスタンスはそのままでは文字列として使えないので、必要な処理が終わったらToStringメソッドでstring型に変換します。

Capacityプロパティ

StringBuilderクラスは内部的に文字列を結合するためのメモリ領域をあらかじめ確保しています。
その領域のサイズを示すのがCapacityプロパティです。
型はint型です。

結合によってCapacityプロパティ以上の文字列の長さになると、自動的に新しいメモリ領域が確保されます。
Microsoftの実装では初期値が「16」で、これを超える文字数の結合が発生する度に倍々に領域が増えていくようです。
(16→32→64→128...)
ただし今後のバージョンアップによって変わる可能性はあります。

CapacityプロパティはStringBuilderクラスのインスタンス生成時にあらかじめ指定することができます。
メモリの確保はそこそこコストが掛かる処理なので、どの程度の文字数になるかがあらかじめわかっている場合は、最初に確保してしまった方が処理が高速になる可能性があります。


//65535文字分の領域をあらかじめ確保
StringBuilder sb = new StringBuilder(65535);

StringBuilderの内容をクリアする

StringBuilderのインスタンスの中身をクリアするには

  • Lengthプロパティに0を代入
  • Clearメソッドを使用する(.NET Framework 4.0以降)
  • 新しいインスタンスを代入する

のいずれかの方法で行います。


StringBuilder sb = new StringBuilder();
sb.Append("This is a pen.");

sb.Length = 0;

sb.Clear();

sb = new StringBuilder();

どの方法でも大した差はないので好みの問題です。
しかし前二つの方法ではCapacityプロパティはそのままで、メモリ領域は確保されたままになることに注意が必要です。
必要に応じてCapacityプロパティもセットしておきます。


StringBuilder sb = new StringBuilder();
sb.Append("This is a pen.");

sb.Length = 0;
sb.Capacity = 16;

新しいインスタンスを代入する場合はCapacityプロパティも既定値にセットされます。

コンストラクター

インスタンス生成時に以下のコンストラクターを使用できます。


//デフォルトの設定
StringBuilder sb1 = new StringBuilder();

//Capacityの設定
StringBuilder sb2 = new StringBuilder(255);

//文字列の初期値の設定
StringBuilder sb3 = new StringBuilder("abc");

//Capacity、MaxCapacityの設定
StringBuilder sb4 = new StringBuilder(255, 65535);

//文字列の初期値とCapacityの設定
StringBuilder sb5 = new StringBuilder("abc", 255);

//文字列の初期値、Capacity、MaxCapacityの設定
StringBuilder sb6 = new StringBuilder("abc", 255);

MaxCapacityプロパティは読み取り専用のプロパティで、使用可能な最大容量を表します。
コンストラクタ生成時に設定可能ですが、文字列の追加等の処理で自動的に書き換わる可能性があります。

メソッド

StringBuilderクラスは文字列を整形するメソッドも用意されています。

ただ、単純な文字列操作の目的でStringBuilderクラスは使用すべきではありません。
StringBuilderクラスはインスタンスの生成およびstring型への変換処理があるため、その点はstring型に比べて余計なコストがかかります。
StringBuilderは同一オブジェクトに対してループ文などで何十何百と文字列操作を行う場合に有効なクラスです。

Append

Appendメソッドは、末尾に文字列を追加します。


StringBuilder sb = new StringBuilder();
sb.Append("abc");   //string型を追加
sb.Append(123);     //int型を追加
Console.WriteLine(sb.ToString());

//'x'を3つ追加
sb.Append('x', 3);
Console.WriteLine(sb.ToString());

//char型配列の1番目から3つ分を追加
sb.Append(new char[] { 'a', 'b', 'c', 'd', 'e' }, 1, 3);
Console.WriteLine(sb.ToString());

//"ABCDEFG"の1文字目から3文字分を追加
sb.Append("ABCDEFG", 1, 3);
Console.WriteLine(sb.ToString());

//いったんクリア
sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder();
sb2.Append("012345");

//StringBuilderインスタンスを追加
sb.Append(sb2);
Console.WriteLine(sb.ToString());

//sb2の1文字目から3文字分を追加
sb.Append(sb2, 1, 3);
Console.WriteLine(sb.ToString());
abc123
abc123xxx
abc123xxxbcd
abc123xxxbcdBCD
012345
012345123

文字列(string型)のほか、C#の組み込み型(int型など)、および別のStringBuilder型のインスタンスが追加可能です。
string型、char型の配列、StringBuilder型のインスタンスはコピー開始位置とコピーする文字数を指定して部分コピーが可能です。
その他、char型は同じ文字を指定の数だけ連続して追加することができます。

AppendLine

AppendLineメソッドは、末尾に文字列と改行を追加します。

引数は空(改行のみが追加される)かstring型が指定可能です。
組み込み型などはToStringメソッドで文字列に変換してから追加できます。


StringBuilder sb = new StringBuilder();
sb.AppendLine("abc");
sb.AppendLine(123.ToString());
Console.WriteLine(sb.ToString());
abc
123

AppendJoin

AppendJoinメソッドは、区切り文字を使用して配列を結合し、末尾に追加します。
このメソッドは.NET Frameworkでは使用できません。


string[] strs = new string[] { "abc", "def" };

StringBuilder sb = new StringBuilder();
sb.AppendJoin('-', strs);
sb.AppendLine();
sb.AppendJoin(", ", strs);
sb.AppendLine();

int[] nums = new int[] { 1, 2, 3 };
sb.AppendJoin(", ", nums.Select(x => x.ToString()));

Console.WriteLine(sb.ToString());
abc-def
abc, def
1, 2, 3

第一引数は区切り文字の指定で、char型かstring型を指定できます。
第二引数は配列のほか、IEnamerable型を指定することもできます。
(その派生クラスであるListクラスなども指定できる)

第二引数の要素数が1つ以下の場合は区切り文字は使用されません。

AppendFormat

AppendFormatメソッドは、フォーマットを指定して文字列を追加します。
フォーマットの動作はstring.Formatメソッドと同等です。


StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}-{1}-{2}", 1, 'a', "ABC");
Console.WriteLine(sb.ToString());
1-a-ABC

Insert

Insertメソッドは、指定の位置に文字列を挿入します。


StringBuilder sb = new StringBuilder();
sb.Insert(0, "abc");
sb.Insert(0, 123);
Console.WriteLine(sb.ToString());

//"AB"を3つ追加
sb.Insert(0, "AB", 3);
Console.WriteLine(sb.ToString());

//いったんクリア
sb = new StringBuilder();

//char型配列を追加
sb.Insert(0, new char[] { 'a', 'b', 'c', 'd', 'e' });
Console.WriteLine(sb.ToString());

//char型配列の1番目から3つ分を追加
sb.Insert(0, new char[] { 'a', 'b', 'c', 'd', 'e' }, 1, 3);
Console.WriteLine(sb.ToString());
123abc
ABABAB123abc
abcde
bcdabcde

第一引数に挿入位置を指定する以外は基本的にAppendメソッドと同じですが、いくつかのオーバーロードが異なります。
string型やStringBuilder型の部分コピーはできません。

Remove

Removeメソッドは、文字列の一部を削除します。


StringBuilder sb = new StringBuilder("0123456789");

//2文字目から3文字分を削除
sb.Remove(2, 3);

Console.WriteLine(sb.ToString());
0156789

第一引数は削除開始位置、第二引数は削除する文字数です。

Replace

Replaceメソッドは、文字列の一部を別の文字、文字列で置き換えます。


StringBuilder sb1 = new StringBuilder("This is his pen.");
StringBuilder sb2 = new StringBuilder("This is his pen.");
StringBuilder sb3 = new StringBuilder("This is his pen.");
StringBuilder sb4 = new StringBuilder("This is his pen.");

//全ての'i'を'x'に置き換え
sb1.Replace('i', 'a');

//全ての"is"を"at"に置き換え
sb2.Replace("is", "at");

//5文字目から2文字分の範囲の'i'を'x'に置き換え
sb3.Replace('i', 'a', 5, 2);

//5文字目から2文字分の範囲の"is"を"at"に置き換え
sb4.Replace("is", "at", 5, 2);

Console.WriteLine(sb1.ToString());
Console.WriteLine(sb2.ToString());
Console.WriteLine(sb3.ToString());
Console.WriteLine(sb4.ToString());
Thas as has pen.
That at hat pen.
This as his pen.
This at his pen.

Clear

Clearメソッドは、インスタンス内の文字列を消去します。
このメソッドは.NET Framework4以降で使用可能です。
(.NET Coreはさ1.0から使用可能)


StringBuilder sb = new StringBuilder("This is a pen.");
sb.Clear();
Console.WriteLine(sb.ToString());

.NET Framework3.5まではLengthプロパティに0を代入することで同等の動作となります。

ToString

ToStringメソッドは、インスタンス内の文字列をstring型に変換します。


StringBuilder sb = new StringBuilder("0123456789");

string s1 = sb.ToString();

//1番目から3文字分を変換
string s2 = sb.ToString(1, 3);

Console.WriteLine(s1);
Console.WriteLine(s2);
0123456789
123

CopyTo

CopyToメソッドは、インスタンス内の文字列の一部を配列またはSpan<char>型にコピーします。


StringBuilder sb = new StringBuilder("0123456789");
char[] chars = new char[5];
Span<char> span = new Span<char>(new char[5]);

//文字列の1番目から3文字分を、charsの0番目からコピー
sb.CopyTo(1, chars, 0, 3);

//文字列の1番目から3文字分をspanにコピー
sb.CopyTo(1, span, 3);

Console.WriteLine(chars);
Console.WriteLine(span.ToString());
123
123

Spanというのは配列などの連続したデータの一部を参照する構造体で、C#7.2から導入された機能です。
Span<char>型のオーバーロードは.NET Core2.0から使用可能で、.NET Frameworkでは使用できません。