【问题标题】:Writing file line by line in C# very slow using streamreader/streamwriter使用streamreader/streamwriter在C#中逐行写入文件非常慢
【发布时间】:2015-09-17 13:01:16
【问题描述】:

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

我正在使用 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/秒的速度工作。这是预期的还是应该加快进程?

【问题讨论】:

  • StreamWriter 在追加一行时不会从头开始写入文件吗?如果每 1.32 亿行重新开始,可以解释为什么要花这么长时间。所以应该是132! (阶乘)操作
  • 您可以逐行编写文件,而无需重新打开它并为每一行寻找结尾,你知道...只需在循环开始时打开输出文件一次。
  • 决心等待 9 小时直到申请完成,然后再重新考虑您当前的流程哈哈
  • 顺便说一下,根据你的Visual Studio版本,你可以使用Profiler来识别瓶颈msdn.microsoft.com/en-us/library/ms182372.aspx
  • @user1 是的,如果您将一行附加到现有文件,而不是重写,您最终会产生几种形式的开销。 1)对象的实例化 2)读取以找到结束指针等

标签: c# streamreader streamwriter


【解决方案1】:

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

AlsoFile.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 所做的事情,因为我猜这就是您的缓慢的来源。对您的代码运行分析器,看看它在哪里花费的时间最多,并将您的工作重点放在那里。

【讨论】:

  • 我还会使用File.ReadLines()foreach 来浏览输入文件。不是更快,但更具可读性。
  • 太棒了!这很好用!速度有了很大的提高。也感谢您提出的计算完成百分比的建议。我将修改为仅报告第 100 次迭代的进度,因此 UI 不会在每个循环中更新。
  • @webworm 每 100 次迭代更新一次也可能是您的问题的一部分,您正在用更新淹没 UI。由于您知道要发送 UI 以及上次发送的内容,因此仅当最后一个值与当前值不同时才向 UI 报告。使用这种方法,您最多可以进行 100 次 UI 更新,使用您的方法,您可以进行 1000 或 10,000 次更新,所有这些都告诉 UI 重复使用您上次报告时告诉它使用的相同数字。
  • @Scott - 我明白了......所以你已经通过仅在百分比不同时发送更新来解决这个问题。非常好!
【解决方案2】:

遵循这个过程:

  1. 实例化读写器
  2. 遍历行,执行接下来的两个步骤
  3. 循环内换行
  4. 在循环中写入更改的行
  5. 处理读写器

这应该比在每行循环上实例化编写器快很多,就像你所做的那样。

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 但我需要计算行数,以便确定完成的百分比
    • 看看 Scott Chamberlain 的解决方案,他很好地解决了这个问题。
    猜你喜欢
    • 1970-01-01
    • 2012-04-14
    • 2018-08-24
    • 1970-01-01
    • 2010-10-28
    • 2011-10-29
    • 2021-05-09
    • 2014-04-03
    • 1970-01-01
    相关资源
    最近更新 更多