【问题标题】:How to read a large (1 GB) txt file in .NET?如何在 .NET 中读取大 (1GB) 文本文件?
【发布时间】:2011-05-15 11:51:13
【问题描述】:

我有一个 1 GB 的文本文件,我需要逐行阅读。最好和最快的方法是什么?

private void ReadTxtFile()
{            
    string filePath = string.Empty;
    filePath = openFileDialog1.FileName;
    if (string.IsNullOrEmpty(filePath))
    {
        using (StreamReader sr = new StreamReader(filePath))
        {
            String line;
            while ((line = sr.ReadLine()) != null)
            {
                FormatData(line);                        
            }
        }
    }
}

FormatData() 中,我检查必须与一个单词匹配的行的起始单词,并基于该增量一个整数变量。

void FormatData(string line)
{
    if (line.StartWith(word))
    {
        globalIntVariable++;
    }
}

【问题讨论】:

  • 您可能想发帖FormatData(或简化版),以防万一。
  • @Matthew:忽略 FormatData(),实际上整个过程很慢,所以为了排除故障,我已经发表了评论。
  • 如果你想要一个快速的解决方案,你不能忽略FormatData,你最好在一个单独的线程中格式化数据,而不是读取数据。
  • 您没有提供太多关于如何访问globalIntVariable 的背景信息。鉴于FormatData 的实现,按顺序读取这些行是否重要?如果不读取多个更大的数据块并同时聚合全局变量会更有效。
  • 您应该发布您已经尝试过的解决方案的实际基准数据。

标签: c# .net streamreader


【解决方案1】:

如果您使用的是 .NET 4.0,请尝试 MemoryMappedFile,它是为此场景设计的类。

否则你可以使用StreamReader.ReadLine

【讨论】:

  • 如果您只是进行顺序读取,那么使用 StreamReader 比使用 MemoryMappedFile 更好,因为它要快得多。内存映射更适合随机访问。
  • 此外,您可能无法创建跨越整个 1 gb 的 ViewAccesor,因此您必须管理它以及解析换行符。对于顺序读取,FileStreams 的速度是 Memory-Mapped 文件的 10 倍。
  • @konrad - 同意,很好的评论,仅供参考,在 O'Reilly 的优秀“C# 4.0 in a Nutshell”中对此进行了一些讨论,第 569 页。对于顺序 I/O 和 1GB文件大小,然后 MemoryMappedFiles 肯定是矫枉过正,可能会减慢速度。
  • @TimSchmelter 你真的希望将 1 gb 文件加载到内存吗?memorymappedfile 有很多用途......我不认为这是其中之一......
  • @dodgy_coder:我会谨慎对待这样的概括。虽然我同意你的最后一句话,但你最好自己measure it
【解决方案2】:

使用 StreamReader 可能是一种方式,因为您不希望整个文件一次在内存中。 MemoryMappedFile 比顺序读取更适合随机访问(顺序读取快十倍,随机访问内存映射快十倍)。

您也可以尝试从 FileOptions 设置为 SequentialScan 的文件流创建您的流阅读器(请参阅 FileOptions Enumeration),但我怀疑它会产生很大的不同。

但是,有一些方法可以使您的示例更有效,因为您可以在与阅读相同的循环中进行格式化。您正在浪费时钟周期,因此如果您想要更高的性能,最好使用多线程异步解决方案,其中一个线程读取数据,另一个线程在数据可用时对其进行格式化。查看可能符合您需求的 BlockingColletion:

Blocking Collection and the Producer-Consumer Problem

如果您想要尽可能快的性能,根据我的经验,唯一的方法是顺序读取尽可能大的二进制数据块并将其并行反序列化为文本,但此时代码开始变得复杂。

【讨论】:

  • +1 限制因素将是从磁盘读取的速度,因此为了提高性能,有不同的线程读取和处理行。
  • @Homde 你写的最后一部分实际上是 StreamReader 在内部做的,那何必呢?
【解决方案3】:

你可以使用LINQ:

int result = File.ReadLines(filePath).Count(line => line.StartsWith(word));

File.ReadLines 返回一个 IEnumerable<String> 懒惰地从文件中读取每一行而不将整个文件加载到内存中。

Enumerable.Count 计算以单词开头的行数。

如果您从 UI 线程调用它,请使用 BackgroundWorker

【讨论】:

    【解决方案4】:

    Probably to read it line by line.

    您不应该尝试通过读取到结束然后处理来强制它进入内存。

    【讨论】:

      【解决方案5】:

      StreamReader.ReadLine 应该可以正常工作。让框架选择缓冲,除非您知道通过分析可以做得更好。

      【讨论】:

      • StreamReader.ReadLine 适用于小文件,但当我尝试将其用于大文件时,有时会非常慢且无响应。
      • @Mathew:贴出的代码看一下,行长不固定,有些时间行只包含 200 个单词,有些时间会是 2000 或更大。
      • 2000 并不是一个大数目。如果我们说的是英语单词,那只有 20 KB。但是,您仍可能希望手动调用FileStream constructor,指定缓冲区大小。我也认为FormatData 实际上可能是问题所在。该方法不会将所有数据都保存在内存中,是吗?
      • @Matthew:我已经评论了 FormatData(),但它仍然很慢,使用和不使用 FormatData() 没有太大区别。
      • @Jeevan 你能定义“慢”吗?如果你在n时间读取[小文件],那么大文件将在n * [大文件]/[小文件]中被读取。也许您正在经历预期的事情?
      【解决方案6】:

      【讨论】:

        【解决方案7】:

        我在Agenty 的生产服务器中遇到了同样的问题,我们在其中看到大文件(有时是 10-25 gb (\t) 制表符分隔的 txt 文件)。经过大量测试和研究,我找到了使用 for/foreach 循环读取小块大文件并使用 File.ReadLines() 设置偏移和限制逻辑的最佳方法。

        int TotalRows = File.ReadLines(Path).Count(); // Count the number of rows in file with lazy load
        int Limit = 100000; // 100000 rows per batch
        for (int Offset = 0; Offset < TotalRows; Offset += Limit)
        {
          var table = Path.FileToTable(heading: true, delimiter: '\t', offset : Offset, limit: Limit);
        
         // Do all your processing here and with limit and offset and save to drive in append mode
         // The append mode will write the output in same file for each processed batch.
        
          table.TableToFile(@"C:\output.txt");
        }
        

        在我的 Github 库中查看完整代码:https://github.com/Agenty/FileReader/

        完全披露 - 我为拥有该图书馆和网站的公司 Agenty 工作

        【讨论】:

          【解决方案8】:

          我的文件超过 13 GB:

          你可以使用我的课:

          public static void Read(int length)
              {
                  StringBuilder resultAsString = new StringBuilder();
          
                  using (MemoryMappedFile memoryMappedFile = MemoryMappedFile.CreateFromFile(@"D:\_Profession\Projects\Parto\HotelDataManagement\_Document\Expedia_Rapid.jsonl\Expedia_Rapi.json"))
                  using (MemoryMappedViewStream memoryMappedViewStream = memoryMappedFile.CreateViewStream(0, length))
                  {
                      for (int i = 0; i < length; i++)
                      {
                          //Reads a byte from a stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
                          int result = memoryMappedViewStream.ReadByte();
          
                          if (result == -1)
                          {
                              break;
                          }
          
                          char letter = (char)result;
          
                          resultAsString.Append(letter);
                      }
                  }
              }
          

          此代码将从头开始读取文件文本到您传递给方法 Read(int length) 的长度并填充 resultAsString 变量。

          它将返回以下文本:

          【讨论】:

          • "它将返回如下文本:"什么文本?
          【解决方案9】:

          我会一次读取 10,000 个字节的文件。然后我会分析这 10,000 个字节并将它们分成几行并将它们提供给 FormatData 函数。

          在多个线程上拆分阅读和行分析的奖励积分。

          我肯定会使用StringBuilder 来收集所有字符串,并且可能会构建一个字符串缓冲区以始终在内存中保留大约 100 个字符串。

          【讨论】:

            猜你喜欢
            • 2016-10-03
            • 2012-06-20
            • 1970-01-01
            • 1970-01-01
            • 2019-04-04
            • 1970-01-01
            • 1970-01-01
            • 2013-02-16
            相关资源
            最近更新 更多