使用 streamreader/streamwriter 在 C# 中逐行写入文件非常慢

Writing file line by line in C# very slow using streamreader/streamwriter

我编写了一个 Winform 应用程序,它读取文本文件的每一行,使用 RegEx 搜索并替换该行,然后写回一个新文件。我选择了 "line by line" 方法,因为有些文件太大而无法加载到内存中。

我正在使用 BackgroundWorker 对象,因此 UI 可以随着作业的进度进行更新。下面是处理文件中行的读取和输出的代码(为简洁起见省略了部分)。

public void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Details of obtaining file paths omitted for brevity

    int totalLineCount = File.ReadLines(inputFilePath).Count();

    using (StreamReader sr = new StreamReader(inputFilePath))
    {
      int currentLine = 0;
      String line;
      while ((line = sr.ReadLine()) != null)
      {
        currentLine++;

        // Match and replace contents of the line
        // omitted for brevity

        if (currentLine % 100 == 0)
        {
          int percentComplete = (currentLine * 100 / totalLineCount);
          bgWorker.ReportProgress(percentComplete);
        }

        using (FileStream fs = new FileStream(outputFilePath, FileMode.Append, FileAccess.Write))
        using (StreamWriter sw = new StreamWriter(fs))
        {
          sw.WriteLine(line);
        }
      }
    }
}

我正在处理的一些文件非常大(8 GB,1.32 亿行)。该过程需要很长时间(一个 2 GB 的文件大约需要 9 个小时才能完成)。它看起来在 58 KB/sec 左右工作。这是预期的还是应该加快进程?

不要在每次循环迭代时关闭并重新打开写入文件,只需在文件循环外打开写入器即可。这应该会提高性能,因为编写器不再需要在每次循环迭代时都查找文件末尾。

另外File.ReadLines(inputFilePath).Count(); 导致您读取输入文件两次并且可能会花费大量时间。而不是基于线的百分比计算基于流位置的百分比。

public void bgWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    // Details of obtaining file paths omitted for brevity

    using (StreamWriter sw = new StreamWriter(outputFilePath, true)) //You can use this constructor instead of FileStream, it does the same operation.
    using (StreamReader sr = new StreamReader(inputFilePath))
    {
      int lastPercentage = 0;
      String line;
      while ((line = sr.ReadLine()) != null)
      {

        // Match and replace contents of the line
        // omitted for brevity

        //Poisition and length are longs not ints so we need to cast at the end.
        int currentPercentage = (int)(sr.BaseStream.Position * 100L / sr.BaseStream.Length);
        if (lastPercentage != currentPercentage )
        {
          bgWorker.ReportProgress(currentPercentage );
          lastPercentage = currentPercentage;
        }
          sw.WriteLine(line);
      }
    }
}

除此之外,您还需要展示 Match and replace contents of the line omitted for brevity 做了什么,我猜这就是您速度慢的原因。 运行 对您的代码进行探查,查看花费最多时间的地方并将您的精力集中在那里。

遵循这个过程:

  1. 实例化reader和编写器
  2. 遍历行,执行接下来的两个步骤
  3. 循环换行
  4. 循环写入更改行
  5. 处置reader和writer

这应该比您在每一行循环中实例化编写器快很多。

我将很快附加一个代码示例。看起来有人在代码示例上抢先一步 - 请参阅@Scott Chamberlain 的回答。

删除顶部的 ReadAllLines 方法,因为读取整个文件只是为了获取行数。