文字列を連結する最も効率的な方法は?
-
09-06-2019 - |
質問
文字列を連結する最も効率的な方法は何ですか?
解決
の 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 });
他のヒント
リコ・マリアーニ, .NET パフォーマンスの第一人者である、 記事 まさにこのテーマについて。それは思っているほど単純ではありません。基本的なアドバイスは次のとおりです。
パターンが次のような場合:
x = f1(...) + f2(...) + f3(...) + f4(...)
それは 1 つの連結であり、処理が速いため、StringBuilder はおそらく役に立ちません。
パターンが次のような場合:
if (...) x += f1(...)
if (...) x += f2(...)
if (...) x += f3(...)
if (...) x += f4(...)
その場合は、おそらく StringBuilder が必要になるでしょう。
この主張を裏付けるさらに別の記事 これは Eric Lippert によるもので、彼は 1 行で実行される最適化について説明しています。 +
連結を詳細に説明します。
文字列の連結には 6 種類あります。
- プラス (
+
) の記号。 - 使用する
string.Concat()
. - 使用する
string.Join()
. - 使用する
string.Format()
. - 使用する
string.Append()
. - 使用する
StringBuilder
.
ある実験で証明されたのは、 string.Concat()
単語が (およそ) 1000 未満の場合は、これが最善の方法であり、単語が 1000 を超える場合は、 StringBuilder
使用すべきです。
詳細については、こちらをご確認ください サイト.
string.Join() と string.Concat()
ここでの string.Concat メソッドは、空の区切り文字を使用した string.Join メソッドの呼び出しと同等です。空の文字列を追加するのは高速ですが、追加しない場合はさらに高速です。 文字列.Concat ここでは方法の方が優れています。
から Chinh Do - StringBuilder が常に高速であるとは限りません:
経験則
3 つ以下の動的文字列値を連結する場合は、従来の文字列連結を使用します。
3 つ以上の動的文字列値を連結する場合は、StringBuilder を使用します。
複数の文字列リテラルから大きな文字列を構築する場合は、@ 文字列リテラルまたはインライン + 演算子のいずれかを使用します。
ほとんど 現時点では StringBuilder が最善の策ですが、その投稿に示されているように、少なくともそれぞれの状況を考慮する必要がある場合もあります。
ループ内で操作している場合は、おそらく StringBuilder が最適です。新しい文字列を定期的に作成するオーバーヘッドを節約できます。ただし、一度だけ実行されるコードでは、String.Concat がおそらく問題ありません。
ただし、Rico Mariani (.NET 最適化の第一人者) クイズを作った その中で彼は最後に、ほとんどの場合 String.Format を推奨すると述べています。
これは、私が大規模な NLP アプリのために 10 年以上かけて進化させた最速の方法です。バリエーションがあります IEnumerable<T>
およびその他の入力タイプ。異なるタイプの区切り文字の有無にかかわらず (Char
, String
) ですが、ここでは次の簡単なケースを示します。 配列内のすべての文字列を連結する 区切り文字なしで単一の文字列にまとめます。ここの最新バージョンは、次の場所で開発および単体テストされています。 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);
このコードは私自身が使用しているものから少し変更されていることに注意してください。原作では私は、 に電話する cpblk IL命令 から C# 実際のコピーを実行します。ここのコードの簡素化と移植性のために、それを P/Invoke に置き換えました。 memcpy
ご覧のとおり、代わりに。x64 で最高のパフォーマンスを実現するには (でもx86ではないかもしれない) を使用するとよいでしょう。 cpblk 代わりにメソッドを使用します。
これから MSDN の記事:
時間とメモリの両方で、StringBuilderオブジェクトの作成に関連するオーバーヘッドがあります。メモリが速いマシンでは、約5つの操作を行っている場合、StringBuilderが価値があります。経験則として、私は10以上の文字列操作が、あらゆるマシンのオーバーヘッドの正当化であり、遅い機でさえも正当化されると思います。
したがって、MSDN を信頼する場合は、10 を超える文字列操作/連結を行う必要がある場合は StringBuilder を使用してください。それ以外の場合は、「+」を使用した単純な文字列連結で問題ありません。
他の回答に加えて、次のことに留意してください。 StringBuilder に割り当てるメモリの初期量を指示できます。.
の 容量 パラメータは、現在のインスタンスによって割り当てられたメモリに格納できる最大文字数を定義します。その値は、 容量 財産。現在のインスタンスに格納される文字数がこれを超える場合 容量 値を指定すると、StringBuilder オブジェクトはそれらを格納するために追加のメモリを割り当てます。
もし 容量 がゼロの場合、実装固有のデフォルト容量が使用されます。
事前割り当てされていない StringBuilder に繰り返し追加すると、通常の文字列を繰り返し連結するのと同じように、大量の不必要な割り当てが発生する可能性があります。
最終的な文字列の長さがわかっていて、それを簡単に計算できる場合、または一般的なケース (割り当てが多すぎることが必ずしも悪いことではない) について知識に基づいた推測ができる場合は、この情報をコンストラクターまたは 容量 財産。 特に StringBuilder を内部で同じことを行う String.Concat などの他のメソッドと比較するパフォーマンス テストを実行するとき。オンラインで見る、比較に StringBuilder の事前割り当てを含まないテストはすべて間違っています。
サイズについてまったく推測できない場合は、事前割り当てを制御するための独自のオプションの引数を持つユーティリティ関数を作成している可能性があります。
を使用する必要があることを指摘することも重要です。 +
連結する場合は演算子 文字列リテラル.
+ 演算子を使用して文字列リテラルまたは文字列定数を連結すると、コンパイラは単一の文字列を作成します。実行時の連結は発生しません。
以下は、複数の文字列を連結するためのもう 1 つの代替ソリューションです。
String str1 = "sometext";
string str2 = "some other text";
string afterConcate = $"{str1}{str2}";
最も効率的なのは、次のように StringBuilder を使用することです。
StringBuilder sb = new StringBuilder();
sb.Append("string1");
sb.Append("string2");
...etc...
String strResult = sb.ToString();
@ジョーネジ:小さなことがいくつかある場合は、String.Concat で問題ありません。しかし、メガバイト規模のデータを連結している場合、プログラムはパンクする可能性があります。
この 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 番目の方が「自然」になる可能性があります。
乾杯。
System.String は不変です。文字列変数の値を変更すると、新しいメモリが新しい値に割り当てられ、以前のメモリ割り当てが解放されます。System.StringBuilder は、変更された文字列に別のメモリ場所を割り当てることなく、さまざまな操作を実行できる変更可能な文字列の概念を持つように設計されました。
別の解決策:
ループ内では、文字列の代わりにリストを使用します。
List<string> lst= new List<string>();
for(int i=0; i<100000; i++){
...........
lst.Add(...);
}
return String.Join("", lst.ToArray());;
とてもとても速いです。
それは実際の使用パターンによって異なります。string.Join、string,Concat、string.Format の間の詳細なベンチマークは、次の場所にあります。 String.Format は集中的なロギングには適していません
(これは実際に私が与えた答えと同じです) これ 質問)
2 つの文字列だけの場合は、StringBuilder を絶対に使用したくないでしょう。StringBuilder のオーバーヘッドが複数の文字列を割り当てるオーバーヘッドよりも小さくなるしきい値がいくつかあります。
したがって、2 ~ 3 文字列を超える場合は、次を使用します。 ダニー・スマーフのコード. 。それ以外の場合は、+ 演算子を使用してください。
それはコード次第でしょう。一般に StringBuilder の方が効率的ですが、いくつかの文字列を連結するだけで、すべてを 1 行で実行する場合は、コードの最適化によって処理される可能性があります。コードがどのように見えるかについて考えることも重要です。大きなセットの場合は StringBuilder を使用すると読みやすくなりますが、小さなセットの場合は StringBuilder は不必要な煩雑さを追加するだけです。