我正在开发一个应用程序,它从巨大的文本文件(约2.5gb)中读取行,将每行操作为特定格式,然后将每行写入文本文件。一旦输出文本文件关闭,程序“批量插入”(sql server)数据到我的数据库。很有效,只是很慢。
我正在使用StreamReaderStreamWriter
由于我必须处理文本,所以我几乎一次只能读一行;但是,我认为如果我收集行,每1000行左右写出一个集合,至少会加快一点速度。问题是(这可能纯粹是由于我的无知),我不能用string[]写一个StreamWriter。在探索stackoverflow和其他互联网之后,我遇到了File.WriteAllLines,它允许我将string[]写入文件,但我认为我的计算机内存不能同时处理2.5gb的存储数据。另外,该文件是创建、填充和关闭的,因此我需要创建大量较小的文件来分解2GB文本文件,只需将它们插入到数据库中。所以我宁愿远离那个选择。
我能想到的一个黑客工作是创建一个StringBuilder并使用AppendLine方法添加每一行以生成一个巨大的字符串。然后我可以将StringBuilder转换为字符串并将其写入文件。
但我的猜测已经够多了。我已经实现的方法可以工作,但是我想知道是否有人可以建议一种更好的方法来将数据块写入文件?

最佳答案

有两件事可以使用StreamWriter提高输出速度。
首先,确保输出文件与输入文件位于不同的物理磁盘上。如果输入和输出在同一个驱动器上,那么通常读操作必须等待写操作,而写操作必须等待读操作。磁盘一次只能做一件事。显然,并不是每个读或写操作都要等待,因为StreamReader会读入缓冲区并解析其中的行,而StreamWriter会写入缓冲区,然后在缓冲区已满时将其推送到磁盘。由于输入和输出文件位于不同的驱动器上,因此读写操作重叠。
我说它们重叠是什么意思?操作系统通常会为您预先读取,因此它可以在您处理文件时缓冲您的文件。当你进行写操作时,操作系统通常会缓冲它并将其惰性地写入磁盘。因此,异步处理的数量有限。
第二件事是增加缓冲区的大小。StreamReaderStreamWriter的默认缓冲区大小为4千字节。因此,每读或写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在内存中构建缓冲区只是不必要的工作,这样做不利于复制通过增加缓冲区大小可以实现的功能,而且如果做得不正确,很容易使程序变慢。
我会警告缓冲区大小不要超过64kb。在某些系统上,使用高达256 KB的缓冲区可以获得稍好的效果,但在其他系统上,性能会显著下降--速度会慢50%!我从未见过系统在缓冲区大于256kb时的性能比在缓冲区为64kb时的性能更好。以我的经验,64kb是最佳选择。
另一件事是你可以使用三个线程:读卡器、处理器和写器。它们与队列通信。这可以将您的总时间从(input-time + process-time + output-time)减少到非常接近max(input-time, process-time, output-time)的程度。使用.net,设置起来非常容易。查看我的博客文章:Simple multithreading, Part 1Simple multithreading, Part 2

09-30 15:42
查看更多