每个月的每一天我都有一个日志文件。这些文件是纯文本,每行都有一些信息,如下面的片段:
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
...
为了处理和显示这些数据,我开发了一个WPF应用程序,它读取这些txt文件,解析行并将这些数据保存在SQLite数据库中。然后,我允许用户进行一些基本的数学运算,如子集的AVG。
由于这些文件太大(每个文件超过300 mb和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
OBS 2:我使用的是Visual Studio 2017,当应用程序在调试模式下运行时,应用程序突然进入中断模式,输出窗口中的消息如下所示:
CLR无法从COM上下文0xb 545 a8转换到COM上下文0xb 544 f0已有60秒.拥有目的上下文/公寓得线程很可能正在执行非泵送等待,或者正在处理运行时间很长得操作,而没有泵送Windows消息.这种情况通常会对性能产生负面影响,甚至可能导致应用程序无法响应或内存使用量持续累积为了避免这个问题,所有单线程单元(STA)线程都应该使用泵送等待原语(例如CoWaitForMultipleHandles),并且在长时间运行的操作期间定期泵送消息。
4条答案
按热度按时间o2rvlv0m1#
尝试使用
StreamReader
,而不是将整个文件一次加载到内存中:b4wnujal2#
您可能会在
ProcessLine
中的LogEntries.Add
处遇到异常,因为您有太多的日志条目,以至于该集合对于内存来说太大了。因此,您应该立即将条目存储到数据库中,而不将它们添加到列表中。
但是你应该只读取一行,然后处理它,然后读取下一行,忘记前一行。
File.ReadAllLines
会一次将所有行读入string[]
,这将占用内存(或导致OutOfMemoryException
)。您可以使用
StreamReader
操作系统File.ReadLines
来代替。u0sqgete3#
您应该使用StreamReader并逐行读取。这将减少阅读时的内存使用。
另外,您应该保留一个相对较小的缓冲区,用于添加到数据库中的经过解析的记录。这可能是大约1000条记录。一旦集合达到1000项,您应该将其写入数据库(最好是在单个事务中进行批量插入),清理集合并移动到下一个输入文件块。
一个好的方法是记住输入文件中的处理位置,以确保应用程序在失败时从上一点恢复。
xxhby3vn4#
C#的功能可让您顺利地行程大型档案,而不会有发生内存不足例外状况的风险。
最佳实践是处理每一行,然后将结果立即返回到输出流、另一个文件甚至数据库,而不会使内存饱和。
首先,使用流读取器逐行迭代到文件中,然后,将结果
yield return
到输出源(也就是说,将结果写入文件或数据库或输出流。这将在每一个新行上彻底释放内存。要更好地理解这一点,请阅读有关收益率回报的内容:https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/yield