3

stringC# のプロジェクトで膨大なデータを扱っています。データを操作するためにどのアプローチを使用すべきかについて混乱していstringます。

最初のアプローチ:

StringBuilder myString = new StringBuilder().Append(' ', 1024);

while(someString[++counter] != someChar)
    myString[i++] += someString[counter];


2 番目のアプローチ:

String myString = new String();

int i = counter;
while(soumeString[++counter] != someChar);
myString = someString.SubString(i, counter - i);

2つのうちどちらがより高速(かつ効率的)ですか? 私が扱っている文字列が巨大であることを考えると。

文字列は既にRAM. 文字列のサイズは、32MB から 1GB までさまざまです。

4

5 に答える 5

4

「巨大な」文字列の場合、すべてをメモリにロードせずに、ストリーミング アプローチを採用することが理にかなっている場合があります。最高の生のパフォーマンスを得るために、ポインター演算を使用して文字列の一部を検索およびキャプチャすることで、速度を少し下げることができる場合があります。

明確にするために、私は2つのまったく異なるアプローチを述べています。

1 - ストリーム
OP には、これらの文字列の大きさは示されていませんが、メモリにロードするのは実際的ではない場合があります。ファイル、DB に接続されたデータ リーダー、アクティブなネットワーク接続などから読み取られている可能性があります。

このシナリオでは、ストリームを開き、順方向に読み取りStringBuilder、基準が満たされるまで入力をバッファリングします。

2 - 安全でない文字操作これには、完全な文字列が
必要です文字列の先頭への char* を非常に簡単に取得できます。

// fix entire string in memory so that we can work w/ memory range safely
fixed( char* pStart = bigString ) 
{
    char* pChar = pStart; // unfixed pointer to start of string
    char* pEnd = pStart + bigString.Length;
}

各文字をインクリメントpCharして調べることができるようになりました。選択したとおりにバッファリングするか (たとえば、隣接する複数の文字を調べたい場合)、またはバッファリングしないかを選択できます。メモリの終了位置を決定したら、操作できるデータの範囲ができました。

C# の安全でないコードとポインター

2.1 - より安全なアプローチ

アンセーフ コードに慣れている場合、それは非常に高速で、表現力があり、柔軟です。そうでない場合でも、同様のアプローチを使用しますが、ポインター演算は使用しません。これは、@supercat が提案したアプローチ、つまり次のアプローチに似ています。

  • char[] を取得します。
  • 1文字ずつ読んでください。
  • 必要に応じてバッファします。StringBuilderこれには適しています。初期サイズを設定し、インスタンスを再利用します。
  • 必要に応じてバッファーを分析します。
  • バッファを頻繁にダンプします。
  • 目的の一致が含まれている場合は、バッファーで何かを行います。

また、安全でないコードに対する必須の免責事項:ほとんどの場合、フレームワーク メソッドの方が優れたソリューションです。それらは安全で、テスト済みで、毎秒何百万回も呼び出されます。安全でないコードは、すべての責任を開発者に負わせます。前提はありません。優れたフレームワーク/OS の市民になるかどうかはあなた次第です (たとえば、不変の文字列を上書きしない、バッファー オーバーランを許可しないなど)。仮定を行わず、セーフガードを削除するため、多くの場合、パフォーマンスが向上します。実際に利点があるかどうか、および利点が十分に大きいかどうかを判断するのは、開発者次第です。

于 2012-08-24T16:22:02.370 に答える
4

IndexOfループ内で個々の文字操作を行うのではなく、使用して、文字列のチャンク全体を結果に追加する必要があります。

StringBuilder myString = new StringBuilder();
int pos = someString.IndexOf(someChar, counter);
myString.Append(someString.SubString(counter, pos));
于 2012-08-24T16:18:44.070 に答える
2

OPからのリクエストごとに、これが私のテスト結果です。

仮定:

  • 大きな文字列はすでにメモリにあり、ディスクから読み取る必要はありません
  • 目標は、ネイティブポインタ/安全でないブロックを使用しないことです
  • 「チェック」プロセスは非常に単純なので、正規表現のようなものは必要ありません。今のところ、単一の文字の比較に単純化しています。以下のコードは、一度に複数の文字を考慮するように簡単に変更できます。これは、2つのアプローチの相対的なパフォーマンスに影響を与えないはずです。

    public static void Main()
    {
        string bigStr = GenString(100 * 1024 * 1024);
    
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < 10; i++)
        {
            int counter = -1;
            StringBuilder sb = new StringBuilder();
            while (bigStr[++counter] != 'x')
                sb.Append(bigStr[counter]);
            Console.WriteLine(sb.ToString().Length);
        }
        sw.Stop();
        Console.WriteLine("StringBuilder: {0}", sw.Elapsed.TotalSeconds);
    
        sw = Stopwatch.StartNew();
        for (int i = 0; i < 10; i++)
        {
            int counter = -1;
            while (bigStr[++counter] != 'x') ;
    
            Console.WriteLine(bigStr.Substring(0, counter).Length);
        }
        sw.Stop();
        Console.WriteLine("Substring: {0}", sw.Elapsed.TotalSeconds);
    }
    
    public static string GenString(int size)
    {
        StringBuilder sb = new StringBuilder(size);
        for (int i = 0; i < size - 1; i++)
        {
            sb.Append('a');
        }
        sb.Append('x');
        return sb.ToString();            
    }
    

結果(リリースビルド、.NET 4):

StringBuilder〜7.9

部分文字列〜1.9

StringBuilderは、さまざまなサイズの文字列で、一貫して3倍以上遅くなりました。

于 2012-08-24T18:17:34.467 に答える
1

IndexOfをより迅速に検索する操作がありsomeCharますが、目的の長さを見つけるための実際の機能はそれよりも複雑であると想定します。そのシナリオでは、 にコピーsomeStringChar[]、検索を実行してから、new String(Char[], Int32, Int32)コンストラクターを使用して最終的な文字列を生成することをお勧めします。a にインデックスを付けると、 orにChar[]インデックスを付けるよりもはるかに効率的になります。通常、必要な文字列のごく一部しか必要としない場合を除き、すべてを にコピーすることは「有利」になります (もちろん、のようなものを単純に使用できます)。StringStringBuilderChar[]IndexOf

文字列の長さが対象の長さよりもはるかに長くなることが多い場合でも、Char[]. をある程度のサイズに事前に初期化してChar[]から、次のようにします。

Char[] temp = 新しい Char[1024];
int i=0;
while (i < theString.Length)
{
  int subLength = theString.Length - i;
  if (subLength > temp.Length) // subLength に他の制約を課すことができます。
    subLength = temp.Length; // ゼロより大きい。
  theString.CopyTo(i, temp, 0, subLength);
  ...配列を操作する
  i+=サブレングス;
}

すべての作業が完了したら、単一の SubString 呼び出しを使用して、元の文字から必要な文字を含む文字列を作成できます。アプリケーションで元の文字とは異なる文字列を作成する必要がある場合はStringBuilder、上記のループ内で and を使用しAppend(Char[], Int32, Int32)て、処理された文字を追加することができます。

また、上記のループ構成の場合、subLengthゼロに縮小されない限り、ループ内の任意のポイントで縮小を決定できることに注意してください。たとえば、文字列に括弧で囲まれた 16 桁以下の素数が含まれているかどうかを調べようとしている場合、開きかっこをスキャンすることから始めることができます。それが見つかった場合、探しているデータが配列を超えて拡張さsubLengthれ、開きかっこの位置に設定され、再ループする可能性があります。このようなアプローチにより、少量の冗長なコピーが発生しますが、それほど多くはなく (多くの場合はゼロ)、ループ間の解析状態を追跡する必要がなくなります。とても便利なパターンです。

于 2012-08-24T16:30:42.567 に答える
-1

文字列を操作するときは、常に StringBuilder を使用する必要があります。これは、文字列が不変であるため、新しいオブジェクトを作成する必要があるたびに発生します。

于 2012-08-24T16:20:53.720 に答える