6

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

4

9 回答 9

8

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

一个快速的 CSV 阅读器

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

于 2009-04-30T21:34:58.900 回答
7

对于基本上任何大小的分配,您都可以获得 OutOfMemoryException。当您分配一块内存时,您实际上是在要求一块具有所请求大小的连续内存。如果不能兑现,您将看到 OutOfMemoryException。

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

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

于 2009-04-30T21:30:07.477 回答
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 提供缓冲,极大地增强了读取。

于 2009-04-30T21:33:56.937 回答
4

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

作为一种折衷方案,您可以尝试使用类似的函数一次读取文件的较大部分(但仍不是全部)StreamReader.ReadBlock(),然后依次处理每个部分。

于 2009-04-30T21:30:35.323 回答
1

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

但是,您说逐行解析比一次全部读取然后进行处理要快几倍。这仅在您采用简单的阻塞读取方法时才有意义,例如(在伪代码中):

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

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

于 2009-04-30T21:43:03.453 回答
0

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

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

于 2009-04-30T21:31:51.887 回答
0

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

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

于 2009-04-30T21:37:16.850 回答
0

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

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

而且我知道,毫无疑问,.NET / CLR 上最好的 CSV 拆分技术就是 这个

该技术从输入 CSV 生成了 +10GB XML 输出,包括广泛的输入过滤器等等,比我见过的任何其他方法都快。

于 2009-05-15T08:32:26.820 回答
0

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

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

于 2010-02-22T23:52:26.780 回答