文字列を連結する最も効率的な方法は?


285

文字列を連結する最も効率的な方法は何ですか?


9
ここでは、すべての関連するケースについて説明しているわけではないため、受け入れられた回答が大幅に不完全であることを目立つように警告します。
usr

@usr確かに... StringBuilder使用例の詳細については、こちらを参照してください
Tamir Vered 2017年

C#6の時点で私のお気に入りは$ "Constant text here {foo} and {bar}" ... String.Formatステロイドのようなものです。パフォーマンスに関しては、1つのライナーでは+and よりも少し遅くString.ConcatなりますがStringBuilder、複数の呼び出しでは、より遅くなりますが、これらよりははるかに優れています。実際には、パフォーマンスの違いは、連結する方法を1つだけ選択する必要がある$場合は、... を使用して文字列補間を選択することです。2つの方法の場合StringBuilderは、ツールボックスに追加します。これらの2つの方法で設定します。
u8it 2017

String.Join以下の答えは+正義を行うものではなく、実際には文字列を連結するための悪い方法ですが、驚くほど高速なパフォーマンスです。なぜ答えが面白いのか。String.ConcatそしてString.Join両方のアレイに作用することができますが、String.Join実際に高速です。どうやら、String.Joinはかなり洗練されており、よりも最適化String.ConcatStringBuilderれています。これは、文字列の長さを最初に計算し、UnSafeCharBufferを使用してこの知識から利益を得る文字列を構築するという点で同様に動作するためです。
u8it 2017

オクラホマので、それは速いですが、String.Joinまた、資源効率の悪い右のようだ配列を構築する必要が?...ことが判明+し、String.Concatとにかく有権者ための構築物の配列を。その結果、手動で配列を作成してフィードするのString.Joinは比較的高速です...しかし、実際にはほぼすべての方法でStringBuilder優れString.Join$いますが、長い文字列ではわずかに遅く、はるかに高速です... String.Joinもし、その場で配列を作成します。
u8it 2017

回答:


154

このStringBuilder.Append()方法は、+演算子を使用するよりもはるかに優れています。しかし、1000回以下の連結を実行すると、String.Join()よりもさらに効率的であることがわかりましたStringBuilder

StringBuilder sb = new StringBuilder();
sb.Append(someString);

唯一の問題String.Joinは、文字列を共通の区切り文字で連結する必要があることです。

編集:として@ryanversawは指摘し、次の区切り文字を作ることができますstring.Empty

string key = String.Join("_", new String[] 
{ "Customers_Contacts", customerID, database, SessionID });

11
StringBuilder相当の起動コストがあり、非常に大きな文字列、または非常に多くの連結で使用した場合にのみ効率的です。特定の状況を見つけることは簡単ではありません。パフォーマンスに問題がある場合は、プロファイリングが役立ちます(ANTSを確認してください)。
Abel、

32
これは、単一行の連結には当てはまりません。myString = "foo" + var1 + "bar" + var2 + "hello" + var3 + "world"を実行すると、コンパイラは自動的にそれをstring.concat呼び出しに変換します。この答えは正しくありません。選択できる優れた答えはたくさんあります
csauve

2
些細な文字列連結には、これまでで最も読みやすいものを使用します。文字列a = b + c + d; ほとんどの場合、StringBuilderを使用するよりも高速ですが、通常、違いは重要ではありません。同じ文字列に繰り返し追加する場合(たとえば、レポートを作成する場合)、または大きな文字列を処理する場合は、StringBuilder(または選択した他のオプション)を使用します。
Swanny

5
なぜあなたは言及しなかったのですstring.Concatか?
Venemo 2013

271

.NET Performanceの第一人者であるRico Marianiが、この件に関する記事を執筆しました。疑わしいほど簡単ではありません。基本的なアドバイスはこれです:

パターンが次のようになっている場合:

x = f1(...) + f2(...) + f3(...) + f4(...)

それは1つの連結であり、それは簡単です、StringBuilderはおそらく役に立たないでしょう。

パターンが次のようになっている場合:

if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)

次に、おそらくStringBuilderが必要です。

この主張を裏付けるさらにもう1つの記事は、エリックリッパートによるもので、1行の+連結で実行される最適化について詳細に説明しています。


1
String.Format()はどうですか?
IronSlug、2015

85

文字列連結には6つのタイプがあります。

  1. プラス(+)記号を使用します。
  2. を使用しstring.Concat()ます。
  3. を使用しstring.Join()ます。
  4. を使用しstring.Format()ます。
  5. を使用しstring.Append()ます。
  6. を使用しStringBuilderます。

実験ではstring.Concat()、単語が1000未満(概算)であり、単語が1000を超える場合にアプローチするのが最良の方法であることが証明されています。StringBuilderを使用する必要があります。

詳しくは、こちらのサイトをご覧ください。

string.Join()とstring.Concat()

ここでのstring.Concatメソッドは、空のセパレータを使用したstring.Joinメソッドの呼び出しと同等です。空の文字列の追加は高速ですが、追加しない場合はさらに高速になるため、ここではstring.Concatメソッドの方が優れています。


4
string.Concat()または+が最善の方法であることが証明されていることをお読みください。はい、記事からこれを取得できますが、ワンクリックで節約できます。したがって、+とconcatは同じコードにコンパイルされます。
brumScouse

私はこの基準を使用して、私の方法をより効率的にしようとしましたが、正確に3つの文字列を連結するだけで済みました。+実際にはそれよりも3ミリ秒高速であることがわかりましたがstring.Concat()string.Concat()アウトレースの前に必要な文字列の量については調べていません+
Gnemlock

59

Chinh Doから-StringBuilderが常に高速であるとは限らない

経験則

  • 3つ以下の動的文字列値を連結する場合は、従来の文字列連結を使用します。

  • 4つを超える動的文字列値を連結する場合は、を使用しますStringBuilder

  • 複数の文字列リテラルから大きな文字列を作成する場合は、@文字列リテラルまたはインライン+演算子を使用します。

ほとんどStringBuilder場合が最善の策ですが、その投稿に示されているように、少なくともそれぞれの状況について考える必要がある場合があります。


8
afaik @は、エスケープシーケンス処理のみをオフにします。msdn.microsoft.com/en-us/library/362314fe.aspx同意
abatishchev

12

ループで動作している場合StringBuilderは、おそらくこれが方法です。新しい文字列を定期的に作成するオーバーヘッドを節約できます。ただし、一度だけ実行されるコードでは、String.Concatおそらく問題ありません。

しかし、Rico Mariani(.NET最適化の第一人者)がクイズ作成し、最後に彼はほとんどの場合、彼が推奨することを述べましたString.Format


私はこれまで何年も、string + stringよりもstring.formatを使用することを勧めてきました。読みやすさの利点は、パフォーマンス上の利点を超える追加の利点だと思います。
スコットローレンス、

1
これが実際の正解です。現在受け入れられているStringBuilderの回答は正しくありません。string.concatまたは+の方が速い単一行の追加については言及されていないためです。ほとんど知られていない事実は、コンパイラが実際に+をstring.concatに変換することです。また、forループまたは複数行連結では、.ToStringが呼び出されたときにのみ追加されるカスタムビルド文字列ビルダーを使用します-StringBuilderが持つ不確定なバッファーの問題を解決します
csauve

2
string.Formatは、どのような状況でも最速の方法ではありません。私はそれが先に来るケースを考案する方法を知りません。
usr

@usr-Ricoは、それが彼の推奨事項であることだけで、それが最速であると明示的に言っていないことに注意してください。デフォルトの選択にしてください。パフォーマンスの問題になる可能性が非常に低い場合は、ローカルの変更をわずかに行うだけで問題に容易に対処できます。通常は、優れた保守性を利用するだけです。」
Adam V

@AdamV質問は最速の方法についてです。パフォーマンス上の理由ではありませんが、これがデフォルトの選択であることには同意しません。不格好な構文になる可能性があります。Resharperは自由に前後に変換できます。
usrは

10

これが、私が大規模なNLPアプリのために10年以上にわたって進化させてきた最速の方法です。IEnumerable<T>さまざまなタイプ(CharString)のセパレーターの有無にかかわらず、他の入力タイプのバリエーションがありますが、ここでは、配列内のすべての文字列を、セパレーターなしの単一の文字列に連結する単純なケースを示し ます。ここの最新バージョンは、C#7および.NET 4.7で開発され、単体テストされています。

より高いパフォーマンスには2つの鍵があります。1つ目は、必要な正確な合計サイズを事前に計算することです。ここに示すように、入力が配列の場合、この手順は簡単です。取り扱いについてIEnumerable<T>の代わりに、それは最初にその合計を計算するための一時的な配列に文字列を収集する価値がある(配列は、呼び出しを避けるために必要とされるToString()副作用の可能性が与えられ、以来、技術的要素ごとに複数回、期待セマンティクスを変更する可能性がありそう「文字列結合」操作の例)。

次に、最終的な文字列の合計割り当てサイズが与えられた場合、結果の文字列をインプレースで構築することにより、パフォーマンスが最大に向上します。これを行うには、String最初はゼロでいっぱいに割り当てられた新しいものの不変性を一時的に一時停止する(おそらく論争の的になる)技術が必要です。しかし、そのような論争はさておき、...

...これは、このページで唯一の一括連結ソリューションであり、コンストラクタによる割り当てとコピーの余分なラウンドを完全に回避することに注意してくださいString

完全なコード:

/// <summary>
/// Concatenate the strings in 'rg', none of which may be null, into a single String.
/// </summary>
public static unsafe String StringJoin(this String[] rg)
{
    int i;
    if (rg == null || (i = rg.Length) == 0)
        return String.Empty;

    if (i == 1)
        return rg[0];

    String s, t;
    int cch = 0;
    do
        cch += rg[--i].Length;
    while (i > 0);
    if (cch == 0)
        return String.Empty;

    i = rg.Length;
    fixed (Char* _p = (s = new String(default(Char), cch)))
    {
        Char* pDst = _p + cch;
        do
            if ((t = rg[--i]).Length > 0)
                fixed (Char* pSrc = t)
                    memcpy(pDst -= t.Length, pSrc, (UIntPtr)(t.Length << 1));
        while (pDst > _p);
    }
    return s;
}

[DllImport("MSVCR120_CLR0400", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe void* memcpy(void* dest, void* src, UIntPtr cb);

このコードには、私が自分で使用しているものから若干の変更が加えられています。オリジナルでは、C#からcpblk IL命令呼び出して、実際のコピーを行います。ここでのコードの単純さと移植性のために、ご覧のとおり、代わりにP / Invokeに置き換えました。x64(ただし、おそらくx86ではない)で最高のパフォーマンスを得るには、代わりにcpblkメソッドを使用できます。memcpy


string.Joinこれらすべてのことをあなたのためにすでにやっています。自分で書く必要はありません。最終的な文字列のサイズを計算し、そのサイズの文字列を作成して、基になる文字配列に書き込みます。プロセスで読みやすい変数名を使用することのボーナスもあります。
サービー

1
@Servyコメントありがとうございます。確かString.Joinに効率的です。イントロでほのめかしたように、ここのコードは、String.JoinCharセパレーターの最適化など)を処理しないか、以前のバージョンの.NETで処理しなかったシナリオで使用する関数ファミリの最も単純な図にすぎません。これは、最も単純な例では選択すべきではなかったと思います。これはString.Join、真空セパレーターの処理の「非効率性」が測定不可能である可能性が高いにもかかわらずです。String.Empty
Glenn Slayden 2017年

確かに、セパレータがない場合はConcat、を呼び出す必要があります。これ正しく行われます。どちらの方法でも、自分でコードを記述する必要はありません。
サービー2017年

7
@Servy このテストハーネスString.Joinを使用し、コードとのパフォーマンスを比較しました。それぞれ最大100ワードサイズの文字列の1,000万回のランダム連結操作の場合、上記のコードは、.NET 4.7を使用しString.Joinx64リリースビルドよりも一貫して34%高速です。OPは「最も効率的な」方法を明示的に要求するため、結果は私の答えが当てはまることを示唆しています。これがあなたの懸念に対処する場合、私はあなたの反対票を再考することを勧めます。
Glenn Slayden 2017年

1
私は最近これをx64フルCLR 4.7.1でベンチマークし、文字列の約2倍の速さであることがわかりました.cpblkまたはgithub.com/を使用する場合は、割り当てられるメモリが約25%少ない(i.imgur.com/SxIpEmL.png)ように参加してくださいジョンハンナ/ムネモシネ
クエンティンスターリン

6

このMSDNの記事から:

時間とメモリの両方で、StringBuilderオブジェクトの作成に関連するオーバーヘッドがいくつかあります。高速メモリを備えたマシンでは、約5つの操作を行う場合、StringBuilderは価値があります。経験則として、10個以上の文字列操作は、どのマシンのオーバーヘッドでも、それより遅いものでも正当化できると言えます。

したがって、MSDNを信頼し、10個を超える文字列操作/連結を行う必要がある場合は、StringBuilderを使用してください。それ以外の場合は、「+」を使用した単純な文字列連結で十分です。



5

他の答えに加えて、StringBuilderに割り当てるメモリの初期量通知できることに注意してください。

容量パラメータは、現在のインスタンスによって割り当てられたメモリに格納することができる文字の最大数を定義します。その値は、Capacityプロパティに割り当てられます。現在のインスタンスに格納される文字数がこの容量値を超える場合、StringBuilderオブジェクトは、それらを格納するために追加のメモリを割り当てます。

場合は容量がゼロであり、実装固有のデフォルトの容量が使用されます。

事前に割り当てられていないStringBuilderに繰り返し追加すると、通常の文字列を繰り返し連結するように、多くの不要な割り当てが発生する可能性があります。

最終的な文字列の長さがわかっている場合、それを簡単に計算できる場合、または一般的なケースについて知識に基づいた推測ができる場合(あまり多くを割り当てることは必ずしも悪いことではありません)、この情報をコンストラクタまたは容量プロパティ。特に、パフォーマンステストを実行して、StringBuilderを、内部で同じことを行うString.Concatなどの他のメソッドと比較する場合。比較でStringBuilderの事前割り当てが含まれていないオンラインで表示されるテストはすべて間違っています。

サイズを推測できない場合は、事前割り当てを制御するための独自のオプションの引数を持つユーティリティ関数を作成している可能性があります。


4

以下は、複数の文字列を連結するもう1つの代替ソリューションです。

String str1 = "sometext";
string str2 = "some other text";

string afterConcate = $"{str1}{str2}";

文字列補間


1
これは実際の一般的な連結方法として驚くほど素晴らしいものです。基本的にString.Formatはより読みやすく、扱いやすくなっています。ベンチマークを行うと、1行連結よりも少し遅くなりますが+String.Concat反復呼び出しで両方の連結よりもはるかに優れており、StringBuilder必要性が低くなります。
u8it 2017

2

最も効率的なのは、次のようにStringBuilderを使用することです。

StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();

@jonezy:小さなことがいくつかある場合は、String.Concatで問題ありません。しかし、メガバイトのデータを連結している場合は、プログラムがたまってしまう可能性があります。


メガバイトのデータのソリューションは何ですか?
Neel

2

この2つのコードを試してみてください。解決策が見つかります。

 static void Main(string[] args)
    {
        StringBuilder s = new StringBuilder();
        for (int i = 0; i < 10000000; i++)
        {
            s.Append( i.ToString());
        }
        Console.Write("End");
        Console.Read();
    }

static void Main(string[] args)
    {
        string s = "";
        for (int i = 0; i < 10000000; i++)
        {
            s += i.ToString();
        }
        Console.Write("End");
        Console.Read();
    }

最初のコードは非常に速く終了し、メモリは十分な量になります。

2番目のコードはおそらくメモリは大丈夫ですが、より長くかかります...はるかに長くなります。したがって、多くのユーザー向けのアプリケーションがあり、速度が必要な場合は、1番目を使用してください。短期間の1つのユーザーアプリのアプリがある場合は、両方を使用するか、2番目のアプリを開発者にとってより「自然」にすることができます。

乾杯。


1

文字列が2つだけの場合は、StringBuilderを使用したくありません。StringBuilderのオーバーヘッドが複数の文字列を割り当てるオーバーヘッドよりも小さいしきい値がいくつかあります。

したがって、2〜3個以上の文字列の場合は、DannySmurfのコードを使用します。それ以外の場合は、+演算子を使用します。


1

System.Stringは不変です。文字列変数の値を変更すると、新しいメモリが新しい値に割り当てられ、以前のメモリ割り当てが解放されます。System.StringBuilderは、変更可能な文字列に個別のメモリ位置を割り当てることなくさまざまな操作を実行できる可変文字列の概念を持つように設計されました。


1

別の解決策:

ループ内では、文字列の代わりにリストを使用します。

List<string> lst= new List<string>();

for(int i=0; i<100000; i++){
    ...........
    lst.Add(...);
}
return String.Join("", lst.ToArray());;

それは非常に高速です。



0

それはコードに依存します。一般に、StringBuilderの方が効率的ですが、いくつかの文字列を連結して1行ですべて実行する場合は、コードの最適化によって処理されます。コードがどのように見えるかを考えることも重要です。大きなセットの場合、StringBuilderは読みやすくなり、小さなセットの場合、StringBuilderは不要な混乱を追加するだけです。

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