如何以高效的方式使用C#编写1GB文件

11

我有一个大小约为1GB的.txt文件(包含超过一百万行),同时有一个字符串列表,我正在尝试从文件中删除存在于字符串列表中的所有行,并创建新文件,但这需要非常长的时间。

using (StreamReader reader = new StreamReader(_inputFileName))
{
   using (StreamWriter writer = new StreamWriter(_outputFileName))
   {
     string line;
     while ((line = reader.ReadLine()) != null)
     {
       if (!_lstLineToRemove.Contains(line))
              writer.WriteLine(line);
     }

    }
  }

如何提高我的代码性能?


13
一个简单的方法是将 _lstLineToRemoveList<string> 转换为 HashSet<string>(假设它还不是哈希集合)。 - ghord
1
速度大约为1Mb/s,这似乎相当慢。你是从哪里读取和写入的?硬盘驱动器?固态硬盘?闪存?当从同一物理驱动器读取和写入时,速度会降低。如果您删除检查并让它写入所有行,那么速度会有多快?如果仍然需要15分钟,那么瓶颈就在文件系统上。如果速度明显较慢,则有一种优化算法的方法。 - Sinatr
1
另一个猜测是将您的List<String>替换为HashSet<String> - Oliver
1
你实际上无法加速IO超出你所拥有的。但正如其他人指出的那样,用HashSet<string>替换List<string>可能会有很大帮助,特别是如果_lstLineToRemove包含数百行。 - Matthew Watson
2
问题:简单复制这个文件需要多长时间?您不能比这个基准更快,所以请先注意这一点。然后,请告诉我,您的应用程序需要多长时间? - DaFi4
显示剩余26条评论
4个回答

4
您可以使用PLINQ并行处理来提高速度,同时将列表更换为哈希集合也会大大加快Contains()检查的速度。HashSet对于只读操作是线程安全的。
private HashSet<string> _hshLineToRemove;

void ProcessFiles()
{
    var inputLines = File.ReadLines(_inputFileName);
    var filteredInputLines = inputLines.AsParallel().AsOrdered().Where(line => !_hshLineToRemove.Contains(line));
    File.WriteAllLines(_outputFileName, filteredInputLines);
}

如果输出文件的顺序与输入文件的顺序无关紧要,您可以删除 .AsOrdered() 并获得更快的速度。

除此之外,您只是受到 I/O 限制,使它变得更快的唯一方法是获取更快的驱动器来运行它。


这与其他答案没有区别,因此同样的评论适用:加载数据是一种IO操作,具有足够的滞后性,可以在读取每行时进行过滤。将所有内容加载到内存中只会极大地减慢过滤速度,并增加由于循环而导致的CPU使用率。日志处理代码不会加载所有内容到内存中。 - Panagiotis Kanavos
@PanagiotisKanavos 这并不会将所有内容加载到内存中,我正在使用ReadLines而不是像其他答案那样使用ReadAllLines,因此处理是流式的,而不是一次性全部加载到内存中。另外,当您在输入时,我已经发布了一个更新,解决了I/O问题。 - Scott Chamberlain
应该是 => !_hshLineToRemove.Contains(... - CSharpie
我怀疑ReadLines比PLINQ更有效。实际上,我会使用TPL Dataflow将读取与写入分离。我还会将目标文件移动到不同的驱动器。我认为这比多线程更能提高性能。只有这样还不够的话,我会添加一个过滤块。 - Panagiotis Kanavos
@PanagiotisKanavos,我本来想写一个数据流程的答案,但觉得这个问题有点过度设计。但是如果您编写了一个数据流程实现,我会为其点赞。 - Scott Chamberlain
显示剩余2条评论

0

代码执行特别慢是因为读取器和写入器从未并行执行。每个都必须等待另一个。

通过使用一个读取线程和一个写入线程,你几乎可以将文件操作的速度提高一倍。在它们之间放置一个BlockingCollection,这样你就可以在线程之间进行通信,并限制在内存中缓冲的行数。

如果计算非常耗费资源(在你的情况下不是),还可以使用第三个线程和另一个BlockingCollection来进行处理。


这已经可以通过Dataflow的ActionBlock直接使用了。 - Panagiotis Kanavos
1
对于新的开发,我老实说不再使用 BlockingCollection 管道了,我已经转向 TPL Dataflow,它提供了与手动 BlockingCollection 基于管道相同的过程,每个工作阶段都有单独的 Task,但它将其封装在漂亮的容器类中,因此您无需处理集合或启动任务。 - Scott Chamberlain
@ScottChamberlain 并且它允许你对读取器进行节流,这样如果写入者很慢,它就不会填满缓冲区的所有行。 - Panagiotis Kanavos

0

不要使用缓冲文本例程。使用二进制、非缓冲库例程,并尽可能将缓冲区大小设置得更大。这是使它最快的方法。


0

你有考虑使用AWK 吗?

AWK 是一个非常强大的文本文件处理工具,你可以找到更多关于如何过滤符合特定条件的行的信息使用 AWK 过滤文本


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接