对于120 MB CSV文件的String.Split()的.NET System.OutOfMemoryException

时间:2009-04-30 21:22:19

标签: .net csv split out-of-memory

我使用C#读取~120 MB纯文本CSV文件。最初我通过逐行读取来进行解析,但最近确定首先将整个文件内容读入内存的速度要快几倍。解析已经非常慢了,因为CSV在引号中嵌入了逗号,这意味着我必须使用正则表达式拆分。这是我发现的唯一可靠的工作:

string[] fields = Regex.Split(line, 
@",(?!(?<=(?:^|,)\s*\x22(?:[^\x22]|\x22\x22|\\\x22)*,)
(?:[^\x22]|\x22\x22|\\\x22)*\x22\s*(?:,|$))");
// from http://regexlib.com/REDetails.aspx?regexp_id=621

为了在将整个内容读入内存后进行解析,我在换行符上进行字符串拆分以获得包含每一行的数组。但是,当我在120 MB文件上执行此操作时,我得到System.OutOfMemoryException。当我的计算机有4 GB RAM时,为什么内存耗尽?有没有更好的方法来快速解析复杂的CSV?

9 个答案:

答案 0 :(得分:8)

除非必须,否则不要滚动自己的解析器。我幸运的是这个:

A Fast CSV Reader

如果没有别的东西,你可以看看引擎盖,看看别人是怎么做的。

答案 1 :(得分:7)

基本上任何大小的分配都可以获得OutOfMemoryException。当你分配一块内存时,你真的需要一个连续的内存来满足所需的大小。如果无法兑现,您将看到OutOfMemoryException。

您还应该知道,除非您运行64位Windows,否则您的4 GB RAM将分为2 GB内核空间和2 GB用户空间,因此您的.NET应用程序无法访问超过2 GB的默认值。

在.NET中执行字符串操作时,由于.NET字符串是不可变的,因此可能会产生大量临时字符串。因此,您可能会看到内存使用率急剧上升。

答案 2 :(得分:5)

如果您将整个文件读入字符串,则应该使用StringReader

StringReader reader = new StringReader(fileContents);
string line;
while ((line = reader.ReadLine()) != null) {
    // Process line
}

这应该与从文件中流式传输相同,区别在于内容已经在内存中。

测试后修改

尝试使用140MB文件,其中处理包括使用line.Length递增长度变量。这在我的电脑上花了大约1.6秒。在此之后我尝试了以下内容:

System.IO.StreamReader reader = new StreamReader("D:\\test.txt");
long length = 0;
string line;
while ((line = reader.ReadLine()) != null)
    length += line.Length;

结果大约是1秒钟。

当然,您的里程可能会有所不同,特别是如果您正在阅读网络驱动器,或者您的处理需要足够长的时间才能让硬盘驱动器寻找其他地方。但是如果你使用FileStream来读取文件而你没有缓冲。 StreamReader提供缓冲,大大增强了阅读。

答案 3 :(得分:4)

您可能无法使用那么多连续的内存分配单个对象,也不能期望能够分配。流式传输是执行此操作的常用方法,但您可能会更慢(尽管我认为它通常不应该慢得那么慢。)

作为妥协,您可以尝试使用类似StreamReader.ReadBlock()的函数同时阅读文件的更大部分(但仍然不是整个文件),并依次处理每个部分。

答案 4 :(得分:1)

正如其他海报所说,OutOfMemory是因为它无法找到所需大小的连续内存块。

但是,你说逐行解析比一次读取所有内容然后进行处理快几倍。这只有在你采用天真的阻塞读取方法时才有意义,例如(在伪代码中):

while(! file.eof() )
{
    string line = file.ReadLine();
    ProcessLine(line);
}

您应该使用流式传输,其中您的流由来自正在读取文件的备用线程的Write()调用填充,因此读取的文件不会被ProcessLine()所阻止,反之亦然。这应该与一次读取整个文件然后进行处理的性能相当。

答案 5 :(得分:0)

您应该尝试使用CLR profiler来确定实际的内存使用情况。可能是除了系统RAM之外还有内存限制。例如,如果这是一个IIS应用程序,则您的内存受应用程序池的限制。

使用此配置文件信息,您可能会发现需要使用更具伸缩性的技术,例如您最初尝试的CSV文件的流式传输。

答案 6 :(得分:0)

堆栈上的内存不足,而不是堆。

你可以尝试重新分解你的应用程序,这样你就可以用更易于管理的“数据块”处理输入,而不是一次处理120MB。

答案 7 :(得分:0)

我同意大多数人的意见,你需要使用流媒体。

我不知道到目前为止是否有人说过,但是你应该看一下exstention方法。

我知道,确切地说,在.NET / CLR上最好的CSV分割技术是 this one

这种技术从输入CSV生成+ 10GB XML输出,包括广泛的输入过滤器,所有这些都比我见过的更快。

答案 8 :(得分:0)

您应该将一个块读入缓冲区并继续处理。然后阅读另一个块,依此类推。

有很多图书馆可以为您高效地完成这项工作。我维护了一个叫CsvHelper的人。您需要处理许多边缘情况,例如逗号或行结尾位于字段中间。