我每个月的每一天都有一个日志文件。这些文件是纯文本,每行包含一些信息,如下面的代码段所示:
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)和常规泵 长时间运行的消息。
答案 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个项目,您应该将其写入数据库(理想情况下,在批量插入的单个事务中),清理集合并移动到下一个输入文件块。
好的方法是记住输入文件中的处理位置,以确保应用程序在发生故障时从最后一点恢复。