我使用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?

有帮助吗?

解决方案

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

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

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

其他提示

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

快速CSV阅读器

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

如果您将整个文件读入字符串,则应该使用 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提供缓冲,大大增强了阅读。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top