导致内存不足异常的大字符串数组(C#)
我已经编写了一个c#win表单应用程序,允许用户打开一个日志(文本)文件并查看数据网格中的日志行。记录数据的应用程序格式,以便用户可以过滤、搜索等 我遇到的问题是,当用户打开大于300mb的日志文件时,应用程序抛出内存不足异常 应用程序首先将所有日志行加载到一个字符串数组中,然后循环遍历日志行,将日志条目对象添加到列表中导致内存不足异常的大字符串数组(C#),c#,memory,heap,out-of-memory,C#,Memory,Heap,Out Of Memory,我已经编写了一个c#win表单应用程序,允许用户打开一个日志(文本)文件并查看数据网格中的日志行。记录数据的应用程序格式,以便用户可以过滤、搜索等 我遇到的问题是,当用户打开大于300mb的日志文件时,应用程序抛出内存不足异常 应用程序首先将所有日志行加载到一个字符串数组中,然后循环遍历日志行,将日志条目对象添加到列表中 var allLogLines = File.ReadAllLines(logPath).ToList(); var nonNullLogLines = allLogLines
var allLogLines = File.ReadAllLines(logPath).ToList();
var nonNullLogLines = allLogLines.Where(l => !string.IsNullOrEmpty(l));
this.ParseLogEntries(nonNullLogLines.ToArray());
这个初始步骤(将日志数据加载到字符串数组中)将占用TaskManager中大约1gb的内存
internal override void ParseLogEntries(string[] logLines)
{
this.LogEntries = new List<LogEntry>();
this.LogLinesCount = logLines.Count();
for (int i = 0; i < this.LogLinesCount; i++)
{
int entryStart = this.FindMessageCompartment(logLines, i);
int entryEnd = this.FindMessageCompartment(logLines, entryStart + 1);
int entryLength = (entryEnd - entryStart) + 1;
if (entryStart + entryLength > this.LogLinesCount)
{
entryLength = this.LogLinesCount - entryStart;
}
var logSection = new string[entryLength];
Array.Copy(logLines, entryStart, logSection, 0, entryLength);
Array.Clear(logLines, i, entryLength - 1);
this.AddLogEntry(logSection);
i = (entryEnd - 1);
}
}
内部重写无效ParseLogEntries(字符串[]日志行)
{
this.LogEntries=新列表();
this.loglinescont=logLines.Count();
对于(int i=0;ithis.loglineScont)
{
entryLength=this.loglinescont-entryStart;
}
var logSection=新字符串[entryLength];
复制(logLines,entryStart,logSection,0,entryLength);
数组。清除(对数线,i,入口长度-1);
此.AddLogEntry(日志部分);
i=(入口端-1);
}
}
AddLogEntry方法将日志条目添加到列表(LogEntries)。for循环设法解析大约50%的日志文件,然后发生内存不足异常。此时任务管理器报告应用程序正在使用大约1.3gb的内存
正如您在上面看到的,我已经添加了Array.Clear以清空已成功解析的日志数据部分,因此我希望随着对象添加到集合中,大型日志数据数组使用的内存量(首先是1gb)会稳步减少,但不会。事实上,这一行对内存使用没有影响,即使我定期添加一个GC collect
读过LOH之后,我假设这是因为堆没有被压缩,因为大数组的部分被置空,所以它总是使用相同的1gb内存,不管其内容如何
是否有任何方法可以减少数据解析时所占用的内存量,或者进行可能的返工以更好地利用内存?我觉得奇怪的是,一个300mb的文本文件在放入字符串数组时,会消耗1gb的内存
谢谢。您可以使用解析单行的
ParseLogEntry(string[]logLines)
方法来代替一次性解析所有日志行的方法
如果将这与一次迭代一行日志文件中的行相结合(例如,通过创建自己的一行),那么首先就可以避免创建大数组string[]logLines
一种方法可能是这样的:
static IEnumerable<string> ReadLines(string filename)
{
using (TextReader reader = File.OpenText(filename))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
// And use the function somewhere to parse the log
var logEntries = new List<LogEntry>()
foreach (string line in ReadLines("log.txt"))
{
logEntries.Add(ParseLogEntry(line));
}
静态IEnumerable可读行(字符串文件名)
{
使用(TextReader=File.OpenText(文件名))
{
弦线;
而((line=reader.ReadLine())!=null)
{
收益率回归线;
}
}
}
//并在某个地方使用该函数来解析日志
var logEntries=新列表()
foreach(ReadLines(“log.txt”)中的字符串行)
{
添加(ParseLogEntry(行));
}
如果您使用的是.NET 4.0或更高版本,您当然可以使用sll在另一个答案中指出的
File.ReadLines
方法,而不是创建自己的方法。首先我看到的是,您正在通过使用以下语句重用并加倍内存使用:
File.ReadAllLines(logPath).ToList();
系统将首先读取所有行,然后将其转换为一个列表,该列表的使用量将增加一倍
我建议您使用以下方式通过streamreader读入该文件:
使用(var sr=newstreamreader(fileName)){//Get Data out here}
这样,一旦您离开语句,内存就会被释放
另外,Array.Copy将使用更多内存,因此请尝试在Using语句中创建并创建所需的对象,或使对象IDisposable,以便垃圾收集器可以节省时间。我建议不要将所有文件加载到内存中,而使用延迟读取。对于>=
.NET4
,您可以利用它来读取文件
使用ReadLines时,可以开始枚举
返回整个集合之前的字符串;因此,当你
如果您使用的是非常大的文件,ReadLines的效率会更高
我知道这不会回答你的问题,但是你可能想考虑不完全加载你的文件到内存。< /P> 在您的情况下,日志文件需要300MB的内存,但如果它需要2.5GB呢?
特别是如果结果是在datagrid中显示,您可能希望改用分页,并在每次需要时从文件中加载一小块数据。字符串需要堆上的连续内存段;当堆上有很多长字符串,并且您试图分配另一个字符串,但没有所需长度的可用段时,应用程序可能会抛出“内存不足” 您的
Array.Clear
行可能没有帮助,因为logSection
字符串不会被垃圾收集,事实上,随着循环的迭代,运行时将很困难,因为在堆上找到一个例如10K的空间比找到10K的空间更难
这就是你的问题所在。至于解决方案,一般来说,我建议采用更懒惰的解决方案。你真的需要主内存中的所有字符串吗?如果是,为什么不至少从一个
StreamReader
读取,而将所有内容加载到string[]logLines
什么是查找消息隔室
?也不要使用数组,使用genericLis
foreach (string line in File.ReadLines(@"path-to-a-file"))
{
// single line processing logic
}