LINQ:大量字符串列表

4

我正在使用LINQ来解析从csv文件中读取的大量字符串列表。

我的代码可以正常处理100MB的文件。

但是在处理500MB的文件时会出现堆栈溢出异常。

我正在使用包含约400万个字符串的列表对代码进行测试。(500MB的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],
                         }
                         );
        }

    }
}

感谢您提供的想法!

不使用LINQ,而是使用通用的foreach/for循环来完成,我相信这样会更快、更简单、更稳定。此外,在处理如此多的项目时,您应该更加仔细地处理代码的每一个细节,因为即使是小的问题也可能会极大地降低性能,所以请谨慎考虑强制转换和创建新对象。 - sll
如果您可以单独处理数据,请将其拆分,但由于LINQ使用了相当多的内存,因此您当然仍然可以按照sll的建议进行操作。 - Vogel612
1个回答

11

尝试逐行解析文件,而不是将其保存到内存中,可以像这样:

var parsedFileData = new List<Metrics>();

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

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

ParseLine 是一个方法,它包含了你的 LINQ 查询的内容,但只操作单行文本,IsLineNeedToBeParsed 是你的 where 语句。如我所注意到的 - 你并没有对多行文本进行任何合并。

避免加载整个文件内容,然后执行带有大量 let 子句的查询 - 它将在执行期间消耗大量内存。

尝试创建纯函数来过滤、选择或聚合数据,如果仍然不满意性能,则尝试通过添加状态、消除冗余计算、添加缓存、添加批次等来优化查询。

还有一个快速点需要指出:你应该使文件加载变成延迟加载,像这样:

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 的《代码整洁之道》书中的一个重要观点:“代码可读性并不是由代码长度决定的”。如果我哪里做错了,请纠正我。

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{}

关于性能方面的建议,我建议使用BufferedStream来包装StreamReader。请参见https://dev59.com/G3I95IYBdhLWcg3wxA8-#9643111。 - Eric J.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接