【问题标题】:How to efficiently write a large text file in C#?如何在 C# 中高效地编写大型文本文件?
【发布时间】:2011-03-25 08:20:09
【问题描述】:

我正在 C# 中创建一个方法,它为 Google Product Feed 生成一个文本文件。提要将包含超过 30,000 条记录,文本文件目前的大小约为 7Mb。

这是我当前使用的代码(为简洁起见,删除了一些行)。

public static void GenerateTextFile(string filePath) {

  var sb = new StringBuilder(1000);
  sb.Append("availability").Append("\t");
  sb.Append("condition").Append("\t");
  sb.Append("description").Append("\t");
  // repetitive code hidden for brevity ...
  sb.Append(Environment.NewLine);

  var items = inventoryRepo.GetItemsForSale();

  foreach (var p in items) {
    sb.Append("in stock").Append("\t");
    sb.Append("used").Append("\t");
    sb.Append(p.Description).Append("\t");
    // repetitive code hidden for brevity ...
    sb.AppendLine();
  }

  using (StreamWriter outfile = new StreamWriter(filePath)) {
      result.Append("Writing text file to disk.").AppendLine();
      outfile.Write(sb.ToString());
  }
}

我想知道 StringBuilder 是否适合这项工作。如果我改用 TextWriter 会有性能提升吗?

我对 IO 性能了解不多,因此我们将不胜感激任何帮助或一般改进。谢谢。

【问题讨论】:

  • 自从我写了这个问题之后,Linq2Csv 项目就诞生了。这是处理我正在编写的代码的更好方法。 nuget.org/packages/LinqToCsv
  • 任何完整的源代码和解决方案?
  • 抱歉,这是为我的一位客户编写的。你真的应该研究一下 Linq2Csv。它会让这类事情变得容易得多。
  • 自我上次对这个问题发表评论以来已经快 5 年了,我强烈推荐 CsvHelper。 joshclose.github.io/CsvHelper

标签: c# text-files io


【解决方案1】:

文件 I/O 操作通常在现代操作系统中得到了很好的优化。您不应该尝试将整个字符串组合成内存中的文件……只需将其逐个写出即可。 FileStream 将负责缓冲和其他性能考虑。

您可以通过移动轻松进行此更改:

using (StreamWriter outfile = new StreamWriter(filePath)) {

到函数的顶部,并摆脱StringBuilder直接写入文件。

您应该避免在内存中建立大字符串有几个原因:

  1. 实际上它的性能可能更差,因为StringBuilder 必须在您写入时增加其容量,从而导致重新分配和复制内存。
  2. 它可能需要比物理分配更多的内存 - 这可能会导致使用比 RAM 慢得多的虚拟内存(交换文件)。
  3. 对于真正的大文件 (> 2Gb),您将耗尽地址空间(在 32 位平台上)并且永远无法完成。
  4. 要将StringBuilder 的内容写入文件,您必须使用ToString(),这实际上会使进程的内存消耗加倍,因为两个副本都必须在内存中保存一段时间。如果您的地址空间碎片足够多,以至于无法分配单个连续的内存块,此操作也可能会失败。

【讨论】:

  • 不错的答案。可以尝试使用 StreamWriter 构造函数重载进行调整,它允许您定义 bufferSize...
  • 您好,感谢您的回答!感谢您抽出宝贵时间就如何处理这种情况添加一些进一步的说明。
  • 5 年后...FileStream 类仍然是编写文本文件的最佳方法吗~7MB?
【解决方案2】:

只需移动using 语句,使其包含整个代码,然后直接写入文件。我认为首先将其全部保存在内存中是没有意义的。

【讨论】:

    【解决方案3】:

    使用 StreamWriter.Write 一次写入一个字符串,而不是将所有内容缓存在 StringBuilder 中。

    【讨论】:

    • 我真的希望你不要让他一次写一个bit
    • 虽然这是一个很好的答案。我有一个大小约为 20Mb 的文件,我面临的问题是 StreamWriter 实际上在末尾放置了一个回车符/换行符。我试图在最后删除那个额外的回车,正如已经指出的那样,StringBuilder 对于性能或大小来说并不是一个很好的解决方案。我试过 StreamReader.Peek() 在它到达末尾之前偷看它。有什么想法吗?
    • @MaximusPeters 您可能在此期间找到了自己的方式,但也许您使用的是WriteLine() 方法而不是Write()
    【解决方案4】:

    这可能很旧,但我有一个文件要写大约 1700 万行 所以我最终每 10k 行对写入进行批处理,类似于这些行

    for (i6 = 1; i6 <= ball; i6++) 
    { //this is middle of 6 deep nest ..
      counter++;
      // modus to get a value at every so often 10k lines
      divtrue = counter % 10000; // remainder operator % for 10k
      //  build the string of fields with \n at the end 
      lineout = lineout + whatever 
      // the magic 10k block here
      if (divtrue.Equals(0))  
      {
         using (StreamWriter outFile = new StreamWriter(@filepath, true))
         { 
             //  write the 10k lines with .write NOT writeline..
             outFile.Write(lineout); 
         } 
         // reset the string so we dont do silly like memory overflow
         lineout = ""; 
      }
    }
    

    在我的情况下,它一次比一行快得多。

    【讨论】:

      猜你喜欢
      • 2014-03-28
      • 1970-01-01
      • 2019-08-02
      • 2018-07-08
      • 2011-04-04
      • 1970-01-01
      • 2011-06-18
      • 2010-12-27
      相关资源
      最近更新 更多