web-dev-qa-db-ja.com

String.Replace()vs. StringBuilder.Replace()

マーカーを辞書の値に置き換える必要がある文字列があります。可能な限り効率的でなければなりません。 string.replaceでループを実行すると、メモリが消費されます(文字列は不変です、覚えておいてください)。これは文字列操作で動作するように設計されているため、StringBuilder.Replace()はより良いでしょうか?

RegExの費用を避けたいと思っていましたが、それがより効率的になるのであれば、そうするべきです。

注:コードの複雑さは気にせず、実行速度と消費するメモリのみを考慮します。

平均の統計:長さ255〜1024文字、辞書の15〜30キー。

73
Dustin Davis

次のコードを使用したRedGate Profilerの使用

class Program
    {
        static string data = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
        static Dictionary<string, string> values;

        static void Main(string[] args)
        {
            Console.WriteLine("Data length: " + data.Length);
            values = new Dictionary<string, string>()
            {
                { "ab", "aa" },
                { "jk", "jj" },
                { "lm", "ll" },
                { "yz", "zz" },
                { "ef", "ff" },
                { "st", "uu" },
                { "op", "pp" },
                { "x", "y" }
            };

            StringReplace(data);
            StringBuilderReplace1(data);
            StringBuilderReplace2(new StringBuilder(data, data.Length * 2));

            Console.ReadKey();
        }

        private static void StringReplace(string data)
        {
            foreach(string k in values.Keys)
            {
                data = data.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace1(string data)
        {
            StringBuilder sb = new StringBuilder(data, data.Length * 2);
            foreach (string k in values.Keys)
            {
                sb.Replace(k, values[k]);
            }
        }

        private static void StringBuilderReplace2(StringBuilder data)
        {
            foreach (string k in values.Keys)
            {
                data.Replace(k, values[k]);
            }
        }
    }
  • String.Replace = 5.843ms
  • StringBuilder.Replace#1 = 4.059ms
  • Stringbuilder.Replace#2 = 0.461ms

ストリングの長さ= 1456

stringbuilder#1はメソッドにstringbuilderを作成しますが、#2はそうではないため、メソッドの外に作業を移動するだけなので、パフォーマンスの違いはほとんど同じになります。文字列ではなく文字列ビルダーで開始する場合、代わりに#2を使用する方法があります。

メモリに関しては、RedGateMemoryプロファイラを使用して、stringbuilderが全体的に勝つ多くの置換操作に入るまで心配する必要はありません。

67
Dustin Davis

これは助けになるかもしれません:

http://blogs.msdn.com/b/debuggingtoolbox/archive/2008/04/02/comparing-regex-replace-string-replace-and-stringbuilder-replace-which-has-better-performance。 aspx

短い答えは、String.Replaceの方が高速であるように見えますが、メモリフットプリント/ガベージコレクションのオーバーヘッドに大きな影響を与える可能性があります。

9
RMD

はい、StringBuilderは速度とメモリの両方を向上させます(基本的に、文字列を操作するたびに文字列のインスタンスを作成しないため、StringBuilderは常に同じオブジェクト)。 MSDNリンク に詳細を示します。

6
Andrei

[String.Replace]よりもstringbuilder.replaceの方が良いでしょうか

はい、はるかに良いです。また、新しい文字列の上限を見積もることができる場合(できるように見えます)、おそらく十分に高速です。

次のように作成すると:

  var sb = new StringBuilder(inputString, pessimisticEstimate);

stringBuilderはバッファを再割り当てする必要がありません。

5
Henk Holterman

データをStringからStringBuilderに変換したり、逆に変換したりするには時間がかかります。単一の置換操作のみを実行している場合、StringBuilderに固有の効率改善によってこの時間を回復できない可能性があります。一方、文字列をStringBuilderに変換してから、その文字列に対して多くのReplace操作を実行し、最後に変換する場合、StringBuilderアプローチはより高速になりがちです。

1
supercat

特定の文字列に平均でいくつのマーカーが存在するかに大きく依存します。

キーの検索のパフォーマンスはStringBuilderとStringの間で似ている可能性がありますが、1つの文字列内の多くのマーカーを置き換える必要がある場合は、StringBuilderが優先されます。

文字列ごとに平均して1つまたは2つのマーカーのみが必要で、辞書が小さい場合は、String.Replaceを使用します。

マーカーが多数ある場合、カスタム構文を定義してマーカーを識別できます。リテラル中括弧の適切なエスケープ規則で中括弧で囲む。その後、解析するアルゴリズムを実装して、文字列の文字を1回繰り返し、見つかった各マーカーを認識して置換できます。または、正規表現を使用します。

1
Joe

文字列全体に対して15〜30の置換操作を実行するのではなく、 trie データ構造のようなものを使用して辞書を保持する方が効率的です。次に、入力文字列を1回ループして、すべての検索/置換を実行できます。

1
Matt Bridges

@DustinDavisの答えの問題は、同じ文字列を再帰的に操作することです。前後のタイプの操作を行うことを計画していない限り、この種のテストでは、操作ケースごとに個別のオブジェクトが必要です。

Web全体で矛盾する答えを見つけたため、独自のテストを作成することにしました。完全に確認したかったのです。私が取り組んでいるプログラムは、多くのテキスト(場合によっては数万行のファイル)を扱います。

そのため、コピーして貼り付けて自分で確認できる、より高速な簡単な方法を次に示します。テストするために独自のテキストファイルを作成する必要があるかもしれませんが、どこからでも簡単にテキストをコピーして貼り付け、自分用に十分な大きさのファイルを作成できます。

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;

void StringReplace_vs_StringBuilderReplace( string file, string Word1, string Word2 )
{
    using( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
    using( StreamReader streamReader = new StreamReader( fileStream, Encoding.UTF8 ) )
    {
        string text = streamReader.ReadToEnd(),
               @string = text;
        StringBuilder @StringBuilder = new StringBuilder( text );
        int iterations = 10000;

        Stopwatch watch1 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @string = @string.Replace( Word1, Word2 );
            else @string = @string.Replace( Word2, Word1 );
        watch1.Stop();
        double stringMilliseconds = watch1.ElapsedMilliseconds;

        Stopwatch watch2 = new Stopwatch.StartNew();
        for( int i = 0; i < iterations; i++ )
            if( i % 2 == 0 ) @StringBuilder = @StringBuilder .Replace( Word1, Word2 );
            else @StringBuilder = @StringBuilder .Replace( Word2, Word1 );
        watch2.Stop();
        double StringBuilderMilliseconds = watch1.ElapsedMilliseconds;

        MessageBox.Show( string.Format( "string.Replace: {0}\nStringBuilder.Replace: {1}",
                                        stringMilliseconds, StringBuilderMilliseconds ) );
    }
}

String.Replace()は、8〜10文字の単語をスワップアウトするたびに約20%高速になりました。独自の経験的証拠が必要な場合は、自分で試してください。

1
Meloviz

ここでの私の2セントは、各メソッドがどのように実行されるかをテストするために数行のコードを書いただけで、予想どおり、結果は「依存」します。

長い文字列の場合はRegexの方が優れているように見えますが、短い文字列の場合はString.Replaceの方が優れています。 StringBuilder.Replaceの使用はあまり役に立たないことがわかります。間違って使用すると、GCの観点からは致命的になる可能性があります(StringBuilderの1つのインスタンスを共有しようとしました)。

StringReplaceTests GitHub repo を確認してください。

1
Zdeněk