10

問題: 非常に大きなファイルで、各行から 3 つの値を取得するために行ごとに解析する必要があります。すべてが機能しますが、ファイル全体を解析するには長い時間がかかります。数秒以内にこれを行うことは可能ですか? 通常、所要時間は 1 分から 2 分です。

例のファイル サイズは 148,208KB です

私はすべての行を解析するために正規表現を使用しています:

ここに私のC#コードがあります:

private static void ReadTheLines(int max, Responder rp, string inputFile)
{
    List<int> rate = new List<int>();
    double counter = 1;
    try
    {
        using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 1024))
        {
            string line;
            Console.WriteLine("Reading....");
            while ((line = sr.ReadLine()) != null)
            {
                if (counter <= max)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
                else if (max == 0)
                {
                    counter++;
                    rate = rp.GetRateLine(line);
                }
            }
            rp.GetRate(rate);
            Console.ReadLine();
        }
    }
    catch (Exception e)
    {
        Console.WriteLine("The file could not be read:");
        Console.WriteLine(e.Message);
    }
}

これが私の正規表現です:

public List<int> GetRateLine(string justALine)
{
    const string reg = @"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$";
    Match match = Regex.Match(justALine, reg,
                                RegexOptions.IgnoreCase);

    // Here we check the Match instance.
    if (match.Success)
    {
        // Finally, we get the Group value and display it.

        string theRate = match.Groups[3].Value;
        Ratestorage.Add(Convert.ToInt32(theRate));
    }
    else
    {
        Ratestorage.Add(0);
    }
    return Ratestorage;
}

解析する行の例を次に示します。通常は約 200,000 行です。

10.10.10.10 - - [27/Nov/2002:16:46:20 -0500] "GET /solr/ HTTP/1.1" 200 4926 789

4

4 に答える 4

16

ヘルプについては、メモリ マップ ファイルタスク並列ライブラリを参照してください。

  1. 複数のランダム アクセス ビューを使用して永続化された MMF を作成します。各ビューはファイルの特定の部分に対応します
  2. のようなパラメーターを使用して解析メソッドを定義しIEnumerable<string>、基本的に解析されていない行のセットを抽象化します
  3. Parse(IEnumerable<string>)タスク アクションとして、1 つの MMF ビューごとに 1 つの TPL タスクを作成して開始します。
  4. 各ワーカー タスクは、解析されたデータをBlockingCollectionタイプの共有キューに追加します。
  5. 他のタスクは BC ( GetConsumingEnumerable() ) をリッスンし、ワーカー タスクによって既に解析されたすべてのデータを処理します。

MSDN のパイプライン パターンを参照してください

このソリューションは.NET Framework >=4

于 2012-12-10T22:57:11.270 に答える
5

現時点では、Regexを呼び出すたびに再作成しますGetRateLine。これは、行を読み取るたびに発生します。

事前にRegex インスタンスを 1 回作成してから非静的Matchメソッドを使用すると、正規表現のコンパイル時間を節約でき、速度が向上する可能性があります。

そうは言っても、おそらく数分から数秒かかることはありません...

于 2012-12-10T22:56:15.783 に答える
2

一見すると、私が試してみたいことがいくつかあります...

まず、ファイル ストリーム バッファを少なくとも 64kb に増やします。

using (var sr = new StreamReader(inputFile, Encoding.UTF8, true, 65536))

次に、ループ内で文字列を使用する代わりに、正規表現を一度構築します。

static readonly Regex rateExpression = new Regex(@"^\d{1,}.+\[(.*)\s[\-]\d{1,}].+GET.*HTTP.*\d{3}[\s](\d{1,})[\s](\d{1,})$", RegexOptions.IgnoreCase);
//In GetRateLine() change to:
Match match = rateExpression.Match(justALine);

3 番目に、Responder.GetRate() がリストまたは配列を返すようにすることで、単一のリスト インスタンスを使用します。

// replace: 'rp.GetRate(rate)', with:
rate = rp.GetRate();

リストを「合理的な」制限に事前に割り当てます。

List<int> rate = new List<int>(10000);

特定のニーズに利用可能で適用可能な場合は、エンコーディングを UTF-8 から ASCII に変更することも検討してください。

コメント

一般に、解析時間を短縮するためにこれが本当に必要な場合は、トークナイザーを構築して正規表現を完全にスキップすることをお勧めします。入力形式はすべて ASCII でかなり単純に見えるため、これは簡単に実行できるはずですが、おそらく正規表現よりも少し脆弱です。最終的には、速度の必要性とコードの信頼性と保守性を比較検討し、バランスを取る必要があります。

手作業による解析の例が必要な場合は、この質問への回答をご覧ください

于 2012-12-10T23:52:20.927 に答える
1

への呼び出しごとに正規表現を再作成する代わりに、事前に作成して、オプションをコンストラクターGetRateLineに渡します。RegexOptions.CompiledRegex(String,RegexOptions)

ファイル全体をメモリに読み込んでみることもできますが、それがボトルネックであるとは思えません。ディスクから最大 100MB を読み取るのに 1 分もかからないはずです。

于 2012-12-10T23:03:44.490 に答える