StringBuilderを使用するのはいつですか?


83

StringBuilderの利点を理解しています。

しかし、2つの文字列を連結したい場合は、StringBuilderなしで行う方が良い(速い)と思います。これは正しいです?

どの時点(文字列の数)でStringBuilderを使用する方が良いですか?


1
これは以前にカバーされたと思います。
Mark Schultheiss


回答:


79

ジェフ・アトウッドによる「マイクロ最適化シアターの悲しい悲劇」を読むことを強くお勧めします。

Simple Concatenation vs. StringBuildervs。他のメソッドを扱います。

さて、あなたがいくつかの数字とグラフを見たいならば、リンクをたどってください;)


はいの場合は+1 !これを心配するのに費やされる時間は、実際に重要かもしれない何かをしないのに費やされる時間です。
グレッグD

8
しかし、あなたの読みは間違っています:ループが含まれていない場合、多くの場合問題ではありませんが、他の場合では問題になる可能性があります
Peter

1
受け入れられた回答の情報が間違っていたため、編集を削除しました。
ピーター

2
そして、それがどれほど重要かを示すために、あなたが参照している記事から:「ほとんどのガベージコレクション言語では、文字列は不変です。2つの文字列を追加すると、両方の内容がコピーされます。追加を続けると、このループが発生します。 、毎回、ますます多くのメモリが割り当てられます。これは、ひどいquadradic n2パフォーマンスに直接つながります」
Peter

2
なぜこれが受け入れられた答えなのですか。リンクをドロップして「これを読んでください」と言うのは良い答えではないと思います
コロブキャニオン

45

しかし、2つの文字列を連結したい場合は、StringBuilderなしで行う方が良い(速い)と思います。これは正しいです?

それは確かに正しいです、あなたは正確に正確に説明されている理由を見つけることができます:

http://www.yoda.arachsys.com/csharp/stringbuilder.html

要約:文字列を一度に連結できる場合は、次のようになります

var result = a + " " + b  + " " + c + ..

コピーが作成される場合のみ、StringBuilderを使用しない方がよいでしょう(結果の文字列の長さは事前に計算されます)。

のような構造の場合

var result = a;
result  += " ";
result  += b;
result  += " ";
result  += c;
..

毎回新しいオブジェクトが作成されるため、StringBuilderを検討する必要があります。

最後に、記事はこれらの親指のルールを要約しています:

経験則

では、いつStringBuilderを使用する必要があり、いつ文字列連結演算子を使用する必要がありますか?

  • 重要なループで連結する場合は、StringBuilderを必ず使用してください。特に、ループ全体で何回反復するかが(コンパイル時に)確実にわからない場合はそうです。たとえば、一度に1文字ずつファイルを読み取り、+ =演算子を使用して文字列を作成すると、パフォーマンスが低下する可能性があります。

  • 1つのステートメントで連結する必要があるすべてのものを(読みやすく)指定できる場合は、必ず連結演算子を使用してください。(連結するものが多数ある場合は、String.Concatを明示的に呼び出すことを検討してください。区切り文字が必要な場合はString.Joinを呼び出してください。)

  • リテラルをいくつかの連結ビットに分割することを恐れないでください-結果は同じになります。たとえば、パフォーマンスを損なうことなく、長いリテラルを複数の行に分割することで、読みやすさを向上させることができます。

  • 連結の次の反復をフィードする以外の目的で連結の中間結果が必要な場合、StringBuilderは役に立ちません。たとえば、姓と名からフルネームを作成し、最後に3番目の情報(ニックネームなど)を追加した場合、StringBuilderを使用するとメリットが得られるのはそうでない場合のみです。他の目的のために(名+姓)文字列が必要です(Personオブジェクトを作成する例のように)。

  • 実行する連結がいくつかあり、それらを別々のステートメントで実行したい場合は、どちらに進むかは重要ではありません。どちらの方法がより効率的かは、関係する文字列のサイズの連結の数と、それらが連結される順序によって異なります。コードの一部がパフォーマンスのボトルネックであると本当に確信している場合は、両方の方法でプロファイルまたはベンチマークを行います。


14

System.Stringは不変のオブジェクトです。つまり、コンテンツを変更するたびに新しい文字列が割り当てられ、これには時間(およびメモリ?)がかかります。StringBuilderを使用すると、新しいコンテンツを割り当てずに、オブジェクトの実際のコンテンツを変更できます。

したがって、文字列に多くの変更を加える必要がある場合は、StringBuilderを使用してください。


8

実際にはそうではありません...大きな文字列を連結する場合、またはループのように多くの連結がある場合は、StringBuilderを使用する必要があります。


1
それは間違いです。StringBuilderループまたは連結が仕様のパフォーマンスの問題である場合にのみ使用する必要があります。
Alex Bagnolini

2
@アレックス:いつもそうじゃないですか?;)いいえ、真剣に、私は常にループ内の連結にStringBuilderを使用していました...しかし、私のループはすべて1,000回以上の反復があります... @ Binary:通常、それはコンパイルする必要がありますstring s = "abcd"、少なくともそれは最後のことです聞いたことがありますが、変数を使用すると、おそらくConcatになります。
ボビー

1
事実は次のとおりです。ほとんどの場合、そうではありません。私はいつも文字列演算子a + "hello" + "somethingelse"を使用していて、心配する必要はありませんでした。問題が発生する場合は、StringBuilderを使用します。しかし、そもそも気にせず、書く時間も少なくて済みました。
Alex Bagnolini

3
大きな文字列ではパフォーマンス上の利点はまったくありません。多くの連結がある場合のみです。
Konrad Rudolph

1
@Konrad:ありませんよろしいです何のパフォーマンス上の利点は?大きな文字列を連結するたびに、大量のデータをコピーします。小さな文字列を連結するたびに、コピーするのは少量のデータだけです。
LukeH 2009

6
  • ループ内で文字列を連結する場合は、通常の文字列の代わりにStringBuilderを使用することを検討する必要があります
  • 単一の連結の場合、実行時間の違いがまったく見られない場合があります

ポイントを証明する簡単なテストアプリは次のとおりです。

class Program
{
    static void Main(string[] args)
    {
        const int testLength = 30000;
        var StartTime = DateTime.Now;

        //TEST 1 - String
        StartTime = DateTime.Now;
        String tString = "test string";
        for (int i = 0; i < testLength; i++)
        {
            tString += i.ToString();
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 2000 ms

        //TEST 2 - StringBuilder
        StartTime = DateTime.Now;
        StringBuilder tSB = new StringBuilder("test string");
        for (int i = 0; i < testLength; i++)
        {
            tSB.Append(i.ToString());
        }
        Console.WriteLine((DateTime.Now - StartTime).TotalMilliseconds.ToString());
        //result: 4 ms

        Console.ReadLine();
    }
}

結果:

  • 30'000回の反復

    • 文字列-2000ミリ秒
    • StringBuilder-4ミリ秒
  • 1000回の反復

    • 文字列-2ミリ秒
    • StringBuilder-1ミリ秒
  • 500回の反復

    • 文字列-0ミリ秒
    • StringBuilder-0ミリ秒

5

言い換えると

それならあなたは3つまで数えなければならない。三はあなたが数えなければならない数であり、数えている数は三でなければならない。4つは数えません。2つも数えません。ただし、3つに進む場合を除きます。3番目の数字である3番目の数字に到達したら、アンティオキアの聖なる手榴弾をロブベストします。

私は通常、3つ以上の文字列を連結するコードのブロックに文字列ビルダーを使用します。


状況によって異なります:Concetanationは1つのコピーのみを作成します: "Russell" + "" + Steen + "。"は、文字列の長さを事前に計算するため、1つのコピーのみを作成します。連結を分割する必要がある場合にのみ、ビルダーについて考え始める必要があります
Peter

4

決定的な答えはなく、経験則だけがあります。私自身の個人的なルールは次のようになります。

  • ループで連結する場合は、常にStringBuilder。を使用してください。
  • 文字列が大きい場合は、常にStringBuilder。を使用してください。
  • 連結コードが整然としていて画面上で読みやすい場合は、おそらく問題ありません。
    そうでない場合は、を使用しStringBuilderます。

私はこれが古いトピックであることを知っていますが、私は学習しか知らず、あなたが「大きな文字列」と見なすものを知りたいですか?
MatthewD 2015

4

ただし、2つの文字列を連結する場合は、StringBuilderを使用しない方が適切で高速であると思います。これは正しいです?

はい。しかし、もっと重要なことは、そのような状況でバニラを使用する方がはるかに読みやすいことStringです。一方、ループで使用することは理にかなっており、連結と同じくらい読みやすくなります。

特定の数の連結をしきい値として引用する経験則には注意が必要です。ループ(およびループのみ)で使用することは、おそらく同じように便利で、覚えやすく、より理にかなっています。


「特定の数の連結をしきい値として引用する経験則には注意が必要です」<これ。また、常識が適用された後、6か月後にあなたのコードに戻ってくる人について考えてみてください。
Phil Cooper

4

意見に影響されない、またはプライドの戦いが続くことのない説明を見つけるのは難しいので、私はこれを自分でテストするためにLINQpadに少しコードを書くことを考えました。

i.ToString()を使用するのではなく、小さいサイズの文字列を使用すると、応答時間が変わることがわかりました(小さなループで表示されます)。

このテストでは、さまざまな反復シーケンスを使用して、時間の測定値を適切に比較可能な範囲に保ちます。

自分で試すことができるように、最後にコードをコピーします(results.Charts ... Dump()はLINQPadの外部では機能しません)。

出力(X軸:テストされた反復回数、Y軸:ティック単位の時間):

反復シーケンス:2、3、4、5、6、7、8、9、10 反復シーケンス:2、3、4、5、6、7、8、9、10

反復シーケンス:10、20、30、40、50、60、70、80 反復シーケンス:10、20、30、40、50、60、70、80

反復シーケンス:100、200、300、400、500 反復シーケンス:100、200、300、400、500

コード(LINQPad 5を使用して記述):

void Main()
{
    Test(2, 3, 4, 5, 6, 7, 8, 9, 10);
    Test(10, 20, 30, 40, 50, 60, 70, 80);
    Test(100, 200, 300, 400, 500);
}

void Test(params int[] iterationsCounts)
{
    $"Iterations sequence: {string.Join(", ", iterationsCounts)}".Dump();

    int testStringLength = 10;
    RandomStringGenerator.Setup(testStringLength);
    var sw = new System.Diagnostics.Stopwatch();
    var results = new Dictionary<int, TimeSpan[]>();

    // This call before starting to measure time removes initial overhead from first measurement
    RandomStringGenerator.GetRandomString(); 

    foreach (var iterationsCount in iterationsCounts)
    {
        TimeSpan elapsedForString, elapsedForSb;

        // string
        sw.Restart();
        var str = string.Empty;

        for (int i = 0; i < iterationsCount; i++)
        {
            str += RandomStringGenerator.GetRandomString();
        }

        sw.Stop();
        elapsedForString = sw.Elapsed;


        // string builder
        sw.Restart();
        var sb = new StringBuilder(string.Empty);

        for (int i = 0; i < iterationsCount; i++)
        {
            sb.Append(RandomStringGenerator.GetRandomString());
        }

        sw.Stop();
        elapsedForSb = sw.Elapsed;

        results.Add(iterationsCount, new TimeSpan[] { elapsedForString, elapsedForSb });
    }


    // Results
    results.Chart(r => r.Key)
    .AddYSeries(r => r.Value[0].Ticks, LINQPad.Util.SeriesType.Line, "String")
    .AddYSeries(r => r.Value[1].Ticks, LINQPad.Util.SeriesType.Line, "String Builder")
    .DumpInline();
}

static class RandomStringGenerator
{
    static Random r;
    static string[] strings;

    public static void Setup(int testStringLength)
    {
        r = new Random(DateTime.Now.Millisecond);

        strings = new string[10];
        for (int i = 0; i < strings.Length; i++)
        {
            strings[i] = Guid.NewGuid().ToString().Substring(0, testStringLength);
        }
    }

    public static string GetRandomString()
    {
        var indx = r.Next(0, strings.Length);
        return strings[indx];
    }
}

3

連結の数(a + b + c ...)を物理的に入力できる限り、大きな違いはありません。Nの2乗(N = 10)は100倍の速度低下であり、それほど悪くはないはずです。

大きな問題は、何百もの文字列を連結する場合です。N = 100では、10000倍の速度低下が発生します。これはかなり悪いです。


2

いつ使うか使わないかという微妙な境界線はないと思います。もちろん、誰かが黄金の状態を出すためにいくつかの広範なテストを実行しない限り。

私にとっては、2つの巨大な文字列を連結するだけの場合は、StringBuilderを使用しません。カウントが不確定なループがある場合は、ループが小さいカウントであっても、そうなる可能性があります。


実際、StringBuilderを使用して2つの文字列を連結することは完全に間違っていますが、それはperfとは関係ありません。テスト-それは単に間違ったことのためにそれを使用しているだけです。
MarcGravell

1

単一の連結は、StringBuilderを使用する価値がありません。私は通常、経験則として5つの連結を使用しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.