读取一个大文本文件(超过400万行)并解析.NET中的每一行

时间:2017-09-25 14:18:34

标签: c# wpf parsing text-files

我每个月的每一天都有一个日志文件。这些文件是纯文本,每行包含一些信息,如下面的代码段所示:

1?2017-06-01T00:00:00^148^3
2?myVar1^3454.33
2?myVar2^35
2?myVar3^0
1?2017-06-01T00:00:03^148^3
...

为了处理和显示这些数据,我正在开发一个读取这些txt文件的WPF应用程序,解析这些行并将这些数据保存在SQLite数据库中。然后,我允许用户进行一些基本的数学运算,如子集的AVG。

由于这些文件太大(每个超过300mb和400万行),我在ProcessLine方法中苦苦寻找内存(据我所知,阅读部分现在还可以)。该方法永远不会完成,应用程序自动进入中断模式。

我的代码:

private bool ParseContent(string filePath)
    {
        if (string.IsNullOrEmpty(FilePath) || !File.Exists(FilePath))
            return false;

        string logEntryDateTimeTemp = string.Empty;

        string [] AllLines = new string[5000000]; //only allocate memory here
        AllLines = File.ReadAllLines(filePath);
        Parallel.For(0, AllLines.Length, x =>
        {
            ProcessLine(AllLines[x], ref logEntryDateTimeTemp);
        });

        return true;
    }

    void ProcessLine(string line, ref string logEntryDateTimeTemp)
    {
        if (string.IsNullOrEmpty(line))
            return;

        var logFields = line.Split(_delimiterChars);

        switch (logFields[0])
        {
            case "1":
                logEntryDateTimeTemp = logFields[1];
                break;
            case "2":
                LogEntries.Add(new LogEntry
                {
                    Id = ItemsCount + 1,
                    CurrentDateTime = logEntryDateTimeTemp,
                    TagAddress = logFields[1],
                    TagValue = Convert.ToDecimal(logFields[2])
                });

                ItemsCount++;
                break;
            default:
                break;
        }
    }

有更好的方法吗?

OBS:我还测试了另外两种读取文件的方法:

        #region StreamReader
        //using (StreamReader sr = File.OpenText(filePath))
        //{
        //    string line = String.Empty;
        //    while ((line = sr.ReadLine()) != null)
        //    {
        //        if (string.IsNullOrEmpty(line))
        //            break;

        //        var logFields = line.Split(_delimiterChars);

        //        switch (logFields[0])
        //        {
        //            case "1":
        //                logEntryDateTimeTemp = logFields[1];
        //                break;
        //            case "2":
        //                LogEntries.Add(new LogEntry
        //                {
        //                    Id = ItemsCount + 1,
        //                    CurrentDateTime = logEntryDateTimeTemp,
        //                    TagAddress = logFields[1],
        //                    TagValue = Convert.ToDecimal(logFields[2])
        //                });

        //                ItemsCount++;
        //                break;
        //            default:
        //                break;
        //        }
        //    }
        //}
        #endregion

        #region ReadLines
        //var lines = File.ReadLines(filePath, Encoding.UTF8);

        //foreach (var line in lines)
        //{
        //    if (string.IsNullOrEmpty(line))
        //        break;      

        //    var logFields = line.Split(_delimiterChars);

        //    switch (logFields[0])
        //    {
        //        case "1":
        //            logEntryDateTimeTemp = logFields[1];
        //            break;
        //        case "2":
        //            LogEntries.Add(new LogEntry
        //            {
        //                Id = ItemsCount + 1,
        //                CurrentDateTime = logEntryDateTimeTemp,
        //                TagAddress = logFields[1],
        //                TagValue = Convert.ToDecimal(logFields[2])                          
        //            });

        //            ItemsCount++;
        //            break;
        //        default:
        //            break;
        //    }             
        //}
        #endregion

OBS2:我正在使用Visual Studio 2017,当应用程序在调试模式下运行时,应用程序突然进入中断模式,并且“输出”窗口中的消息显示如下:

  

CLR无法从COM上下文0xb545a8转换为COM   上下文0xb544f0持续60秒。拥有目标的线程   上下文/公寓最有可能是做一次非抽水等待或   处理非常长时间运行的操作而无需泵送Windows   消息。这种情况通常会对性能产生负面影响   甚至可能导致应用程序变得无响应或内存   使用量随着时间的推移不断累积。所有人都要避免这个问题   单线程公寓(STA)线程应该使用抽水等待   原语(如CoWaitForMultipleHandles)和常规泵   长时间运行的消息。

3 个答案:

答案 0 :(得分:2)

尝试使用StreamReader,而不是一次将整个文件加载到内存中:

using (System.IO.StreamReader sr = new System.IO.StreamReader(filePath))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        //..
    }
}

答案 1 :(得分:2)

您可能会在LogEntries.Add的{​​{1}}处获得异常,因为您有太多日志条目,因此此集合对于内存来说太大了。

所以你应该立即将条目存储到数据库中,而不将它们添加到列表中。

但是你应该只读一行,然后处理它,然后读下一行并忘记前一行。 ProcessLine会立即将所有行读入File.ReadAllLines,占用内存(或导致string[])。

您可以使用OutOfMemoryException操作系统StreamReader代替。

答案 2 :(得分:1)

您应该使用StreamReader并逐行阅读。这将减少读取的内存使用量。

此外,您应该将相对较小的已解析记录缓冲区添加到数据库中。这可能是大约1000条记录。一旦集合达到1000个项目,您应该将其写入数据库(理想情况下,在批量插入的单个事务中),清理集合并移动到下一个输入文件块。

好的方法是记住输入文件中的处理位置,以确保应用程序在发生故障时从最后一点恢复。