如何将淫秽数据写入文件?

时间:2013-07-25 23:09:17

标签: c# performance optimization

我正在开发一个应用程序,它从大量文本文件(~2.5 GB)读取行,将每行操作为特定格式,然后将每行写入文本文件。一旦输出文本文件关闭,程序“批量插入”(SQL Server)将数据存入我的数据库。它有效,它只是很慢。

我正在使用StreamReaderStreamWriter

由于我必须操纵文本,我几乎一直在阅读一行。但是,我认为,如果我制作了一系列线条并且每1000行左右写出一个集合,那么它至少可以加快速度。问题是(这可能纯粹是出于我的无知)我无法使用string[]StreamWriter。在探索 StackOverflow 和其他互联网之后,我遇到了File.WriteAllLines,它允许我将string[]写入文件,但我不认为我的计算机的内存可以处理2.5一次存储GB数据。此外,文件已创建,填充和关闭,因此我必须制作大量较小的文件来分解2 GB文本文件,只是将它们插入数据库。所以我宁愿远离这个选择。

我能想到的一个黑客工作是制作一个StringBuilder并使用AppendLine方法添加每一行来制作一个巨大的字符串。然后我可以将StringBuilder转换为字符串并将其写入文件。

但是我的推测足够了。我已经实现的方法有效,但我想知道是否有人可以建议更好的方法将数据块写入文件?

3 个答案:

答案 0 :(得分:11)

使用StreamWriter,有两件事可以提高输出速度。

首先,确保输出文件与输入文件位于不同的物理磁盘上。如果输入和输出在同一驱动器上,那么读取通常必须等待写入和写入必须等待读取。磁盘一次只能做一件事。显然不是每次读取或写入等待,因为StreamReader读入缓冲区并从中解析行,StreamWriter写入缓冲区然后将其推送到磁盘当缓冲区已满。将输入和输出文件放在不同的驱动器上,您的读写重叠。

我的意思是他们重叠了吗?操作系统通常会提前读取,因此它可以在您处理时缓冲文件。当你进行写操作时,操作系统通常会缓冲它并将其懒惰地写入磁盘。因此,正在进行一些有限的异步处理。

第二件事是增加缓冲区大小。 StreamReaderStreamWriter的默认缓冲区大小为4 KB。因此,每次读取或写入4K都会产生操作系统调用。并且很可能是磁盘操作。

如果将缓冲区大小增加到64K,那么操作系统调用次数减少16次,磁盘操作次数减少16次(严格来说不是真的,而是关闭)。使用64K缓冲区可以减少超过25%的I / O时间,这很简单:

const int BufferSize = 64 * 1024;
var reader = new StreamReader(filename, Encoding.UTF8, true, BufferSize);
var writer = new StreamWriter(filename, Encoding.UTF8, BufferSize);

这两件事将比你能做的任何其他事情都更快地加速你的I / O.尝试使用StringBuilder在内存中构建缓冲区只是不必要的工作,通过增加缓冲区大小来复制可以实现的功能,并且错误地完成可以轻松地使程序更慢

我会提醒大小超过64 KB的缓冲区大小。在某些系统上,缓冲区高达256 KB会获得稍微好一些的结果,但在其他系统上,性能会大大降低 - 速度会慢50%!我已经从未看到系统在大于256 KB的缓冲区中的性能优于使用64 KB的缓冲区。根据我的经验,64 KB是最佳选择。

您可以做的另一件事是使用三个线程:阅读器,处理器和编写器。他们与队列进行通信。这可以将您的总时间从(input-time + process-time + output-time)减少到非常接近max(input-time, process-time, output-time)的时间。使用.NET,真的易于设置。请参阅我的博文:Simple multithreading, Part 1Simple multithreading, Part 2

答案 1 :(得分:9)

根据docsStreamWriter默认情况下每次写入后都不会自动刷新,因此它被缓冲。

你也可以在File类上使用一些惰性方法,如下所示:

File.WriteAllLines("output.txt", 
    File.ReadLines("filename.txt").Select(ProcessLine));

其中ProcessLine的声明如下:

private string ProcessLine(string input) {
    string result =         // do some calculation on input
    return result;
}

由于ReadLines是惰性且WriteAllLines具有延迟重载,因此它会传输文件而不是尝试读取整个文件。

答案 2 :(得分:1)

如何构建要编写的字符串?

这样的东西
int cnt = 0;
StringBuilder s = new StringBuilder();
while(line = reader.readLine())
{
  cnt++;
  String x = (manipulate line);
  s.append(x+"\n");
  if(cnt%10000 == 0)
  {
     StreamWriter.write(s);
     s=new StringBuilder();
  }
}

编辑因为下面的评论是正确的,应该使用stringbuilder。