4

LINQ を使用して、csv ファイルから読み取った文字列の大きなリストを解析しています。私のコードは 100MB のファイルで問題なく動作します。しかし、スタックオーバーフロー例外のため、それを超えることはできません。リスト内の文字列の数が約 400 万である 500 MB のファイルでコードをテストしています (500 MB の csv ファイルで約 400 万行)。

    public List<Metrics> MetricsParser(DateTime StartDate, TimeSpan StartTime, DateTime EndDate, TimeSpan EndTime,int dateIndex,int timeIndex)
    {
        DateTime sd = StartDate;
        DateTime ed = EndDate;
        TimeSpan st = StartTime;
        TimeSpan et = EndTime;
        StreamReader streamReader;
        List<string> lines = new List<string>();


        try
        {
            streamReader = new StreamReader("file.csv");
            lines.Clear();
            while (!streamReader.EndOfStream)
                lines.Add(streamReader.ReadLine());
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (streamReader != null)
                streamReader.Close();
        }

        IEnumerable<Metrics> parsedFileData = null;
        parsedFileData = from line in lines
                         let log = line.Split(",")
                         where (!(line.StartsWith("#")) & (line.Length > 0))
                         let dateVal = _utility.GetDateTime(dateformatType, log[(int)dateIndex], log[(int)timeIndex])
                         let timeVal = _utility.GetTime(log[(int)timeIndex], timeformatType)
                         where (dateVal >= new DateTime(sd.Year, sd.Month, sd.Day, st.Hours, st.Minutes, st.Seconds)
                                 & dateVal <= new DateTime(ed.Year, ed.Month, ed.Day, et.Hours, et.Minutes, et.Seconds))
                         select new Metrics()
                         {
                             Date = dateVal,
                             Metrics1 = log[(int)Metrics1Index],
                             Metrics2 = (Metrics2Index != null) ? log[(int)Metrics2Index] : "default",
                             Metrics3 = (log[(int)Metrics3Index] == null || log[(int)Metrics3Index] == "") ? "-" : log[(int)Metrics3Index],
                             Metrics4 = (log[(int)Metrics4Index] == null || log[(int)Metrics4Index] == "") ? "-" : log[(int)Metrics4Index],
                             Metrics5 = (log[(int)Metrics5Index] == null || log[(int)Metrics5Index] == "") ? "-" : log[(int)Metrics5Index],
                             Metrics6 = (log[(int)Metrics6Index] == null || log[(int)Metrics6Index] == "") ? "-" : log[(int)Metrics6Index],
                             Metrics7 = (log[(int)Metrics7Index] == null || log[(int)Metrics7Index] == "") ? "-" : log[(int)Metrics7Index],
                             Metrics8 = (log[(int)Metrics8Index] == null || log[(int)Metrics8Index] == "") ? "-" : log[(int)Metrics8Index],
                             Metrics9 = (log[(int)Metrics9Index] == null || log[(int)Metrics9Index] == "") ? "-" : log[(int)Metrics9Index],
                         };
        return parsedFileData.ToList();
    }

より大きなデータでタスクを達成する方法についてのアイデア。

いくつかの提案に従って以下のように試しましたが、スタックオーバーフロー例外を克服できませんでした!

try
{
    streamReader = new StreamReader("file.csv");
    while (!streamReader.EndOfStream)
    {
        var line = streamReader.ReadLine();
        if (!(line.StartsWith("#")) & (line.Length > 0))
        {
            var log = line.Split(",");
            var dateVal = _utility.GetDateTime(dateformatType, log[(int)dateIndex], log[(int)timeIndex]);
            parsedData.Add(
                         new Metrics()
                         {
                             Date = dateVal,
                             Metrics1 = log[(int)Metrics1Index],
                             Metrics2 = (Metrics2Index != null) ? log[(int)Metrics2Index] : "default",
                             Metrics3 = (log[(int)Metrics3Index] == null || log[(int)Metrics3Index] == "") ? "-" : log[(int)Metrics3Index],
                             Metrics4 = (log[(int)Metrics4Index] == null || log[(int)Metrics4Index] == "") ? "-" : log[(int)Metrics4Index],
                             Metrics5 = (log[(int)Metrics5Index] == null || log[(int)Metrics5Index] == "") ? "-" : log[(int)Metrics5Index],
                             Metrics6 = (log[(int)Metrics6Index] == null || log[(int)Metrics6Index] == "") ? "-" : log[(int)Metrics6Index],
                             Metrics7 = (log[(int)Metrics7Index] == null || log[(int)Metrics7Index] == "") ? "-" : log[(int)Metrics7Index],
                             Metrics8 = (log[(int)Metrics8Index] == null || log[(int)Metrics8Index] == "") ? "-" : log[(int)Metrics8Index],
                             Metrics9 = (log[(int)Metrics9Index] == null || log[(int)Metrics9Index] == "") ? "-" : log[(int)Metrics9Index],
                         }
                         );
        }

    }
}

アイデアをありがとう!

4

1 に答える 1

11

このように、メモリに保存するのではなく、ファイルを 1 行ずつ解析してみてください。

var parsedFileData = new List<Metrics>();

while (!streamReader.EndOfStream)
{
    var line = streamReader.ReadLine();

    if(IsLineNeedToBeParsed(line))
        parsedFileData.Add(ParseLine(line));
} 

LINQ クエリのコンテンツを含むメソッドはどこParseLineにありますが、単一行で動作しIsLineNeedToBeParsedwhere句です。気づいたように、行の結合は行いません。

letファイルの内容全体をロードしてから、多数の句を含む大きなクエリを実行することは避けてください。実行中に大量のメモリが消費されます。

集計データをフィルタリングして選択する純粋な関数を作成してみてください。それでもパフォーマンスが気に入らない場合は、状態を追加してクエリを最適化し、冗長な計算を排除し、おそらくキャッシュし、バッチを追加するなどしてください。

1 つの簡単なポイント: 次のように、ファイルの読み込みを遅延させる必要があります。

private IEnumerable<string> GetAllLines(string path)
{
    using (StreamReader streamReader = new StreamReader(path))
    {
        while (!streamReader.EndOfStream)
        {
            yield return streamReader.ReadLine();
        } 
    }
}

LINQ次に、次のようなクエリから呼び出すことができます

from line in GetAllLines("file.csv")

すべての行はオンデマンドでロードされ、実行中のメモリ消費量は比較的一定です。

アップデート:

内部File.ReadLines(string path)で作成することにより、ファイルを遅延して読み取ることがわかりました。ReadLinesIteratorしたがって、LINQ クエリ内でこの呼び出しを使用できます。

勇気を出してコードを少しリファクタリングしました。まだチェックを追加する必要があることに注意してください。これは最終バージョンではありません。一般的な考え方を示したいだけです。また、私はそれをコンパイルしていないことに注意してください。パーサーの状態にアクセスでき、その型と値について何も知らないためです。コードはあなたのものより少し長いですが、「コードを読みやすくするのは短さではありません」という重要なポイントがある Robert Martin による Clean Code の本を決して忘れません。どこか間違っている場合は修正してください。

public List<Metrics> MetricsParser(DateTime StartDate, TimeSpan StartTime, DateTime EndDate, TimeSpan EndTime,int dateIndex,int timeIndex)
{
    DateTime sd = StartDate;
    DateTime ed = EndDate;
    TimeSpan st = StartTime;
    TimeSpan et = EndTime;
    List<Metrics> parsedFileData = new List<Metrics>();

    using (StreamReader streamReader = new StreamReader("file.csv"))
    {
        while (!streamReader.EndOfStream)
        {
            var line = streamReader.ReadLine();

            if(IsLineNeedToBeParsed(line))
                parsedFileData.Add(ParseLine(line));
        } 
    }

    return parsedFileData;
}

private bool IsLineNeedToBeParsed(string line)
{
    return !(line.StartsWith("#")) && (line.Length > 0) && IsInDateRange(line);
}

private bool IsInDateRange(string line)
{
    var dateVal = GetDateTime(line);
    return dateVal >= new DateTime(sd.Year, sd.Month, sd.Day, st.Hours, st.Minutes, st.Seconds)
         & dateVal <= new DateTime(ed.Year, ed.Month, ed.Day, et.Hours, et.Minutes, et.Seconds);
}

private Metrics ParseLine(string line)
{
    var log = line.Split(',');
    var time = _utility.GetTime(log[(int)timeIndex], timeformatType);
    var dateVal = GetDateTime(line);
    return new Metrics{  /* fill values here */ }
}

private string[] GetDateTime(string line)
{
    var log = line.Split(',');
    return _utility.GetDateTime(dateformatType, log[(int)dateIndex], log[(int)timeIndex]);
}

public class Metrics{}
于 2013-01-17T10:09:06.180 に答える