【问题标题】:Best way to read multiple very large files读取多个非常大的文件的最佳方式
【发布时间】:2013-07-05 06:28:22
【问题描述】:

我需要帮助找出最快的方法来读取大约 80 个文件,每个文件中的行超过 500,000 行,并将每个输入文件的行作为主文件中的一列写入一个主文件。主文件必须写入记事本等文本编辑器,而不是 Microsoft 产品,因为它们无法处理行数。

例如,主文件应如下所示:

File1_Row1,File2_Row1,File3_Row1,...

File1_Row2,File2_Row2,File3_Row2,...

File1_Row3,File2_Row3,File3_Row3,...

等等

到目前为止,我已经尝试了 2 个解决方案:

  1. 创建一个锯齿状数组以将每个文件的内容保存到一个数组中,然后在读取所有文件中的所有行后,写入主文件。此解决方案的问题是 Windows 操作系统内存会引发错误,即正在使用过多的虚拟内存。
  2. 为读取特定行号的 80 个文件中的每一个动态创建一个读取器线程,一旦所有线程完成读取一行,将这些值组合并写入文件,并对所有文件中的每一行重复。这个解决方案的问题是它非常非常慢。

有没有人有更好的解决方案来快速读取这么多大文件?

【问题讨论】:

  • "... 像记事本,而不是 Microsoft 产品..." - 我不想告诉你,但记事本是 Microsoft 产品
  • ...文本文件就是文本文件。换句话说,没有NotePadFileStream

标签: c# performance file file-io text-files


【解决方案1】:

最好的方法是打开输入文件,每个文件使用StreamReader,输出文件使用StreamWriter。然后你遍历每个阅读器并读取一行并将其写入主文件。这样你一次只加载一行,所以应该有最小的内存压力。我能够在 37 秒内复制 80 ~ 500,000 个行文件。一个例子:

using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;

class MainClass
{
    static string[] fileNames = Enumerable.Range(1, 80).Select(i => string.Format("file{0}.txt", i)).ToArray();

    public static void Main(string[] args)
    {
        var stopwatch = Stopwatch.StartNew();
        List<StreamReader> readers = fileNames.Select(f => new StreamReader(f)).ToList();

        try
        {
            using (StreamWriter writer = new StreamWriter("master.txt"))
            {
                string line = null;
                do
                {
                    for(int i = 0; i < readers.Count; i++)
                    {
                        if ((line = readers[i].ReadLine()) != null)
                        {
                            writer.Write(line);
                        }
                        if (i < readers.Count - 1)
                            writer.Write(",");
                    }
                    writer.WriteLine();
                } while (line != null);
            }
        }
        finally
        {
            foreach(var reader in readers)
            {
                reader.Close();
            }
        }
        Console.WriteLine("Elapsed {0} ms", stopwatch.ElapsedMilliseconds);
    }
}

我假设所有输入文件的行数都相同,但是当至少一个文件为您提供数据时,您应该添加逻辑以继续阅读。

【讨论】:

    【解决方案2】:

    使用Memory Mapped 文件似乎适合您。不会对应用程序的内存造成压力的东西同时保持 IO 操作的良好性能。

    这里有完整的文档:Memory-Mapped Files

    【讨论】:

    • 作为答案(而不是评论),这有点像回答问题,“你如何建造房子?”“使用砖块。此链接上有一些关于砖块的好指南"。正如mike z's answer 所展示的,至少有一种方法可以使用OP 已经知道的工具来回答这个问题;问题更多的是关于如何使用这些工具,而不是使用哪些工具。
    • @shambulator:在这里看不到任何无效的东西。我们正在谈论的技术不是简明扼要的答案。我可以复制/粘贴文档提供的代码,但我认为文档更好地展示了我们正在讨论的功能的详细说明。
    • 但据我了解 msdn 文章,内存映射文件仍然需要开发人员处理内存溢出,对吧?
    • @KaiHartmann 完全正确; OP 不会神奇地通过用 MMF 替换文件 IO API 来解决内存管理问题。该问题暴露了对OP的一定程度的理解;像这样的答案 - 虽然大致正确 - 假设更多的背景知识。如果 OP 知道的足够多,可以从 MMF 上的 MSDN 文档中吸收他们需要的一切,他们就不会问这个问题。
    【解决方案3】:

    如果您的计算机上有足够的内存,我会使用 Parallel.Invoke 构造并将每个文件读入预先分配的数组,例如:

    string[] file1lines = new string[some value];
    string[] file2lines = new string[some value];
    string[] file3lines = new string[some value];
    
    Parallel.Invoke(
    () =>
    {
       ReadMyFile(file1,file1lines);
    },
    () =>
    {
       ReadMyFile(file2,file2lines);
    },
    () =>
    {
       ReadMyFile(file3,file3lines);
    }
    );
    

    每个 ReadMyFile 方法都应该只使用以下示例代码,according to these benchmarks,这是读取文本文件的最快方式:

    int x = 0;
    using (StreamReader sr = File.OpenText(fileName))
    {
            while ((file1lines[x] = sr.ReadLine()) != null)
            {
                   x += 1;
            }
    }
    

    如果您需要在编写最终输出之前处理每个文件中的数据,请read this article 以最快的方式进行操作。

    然后,您只需要一种方法即可根据需要将每个 string[] 的内容写入输出。

    【讨论】:

      【解决方案4】:

      有一组打开的文件句柄。循环遍历该数组并将每个文件中的一行读入字符串数组。然后将此数组合并到主文件中,在末尾追加一个换行符。

      这与您的第二种方法不同,它是单线程的,不会读取特定的行,但总是读取下一行。

      当然,如果文件的行数少于其他文件,则需要防错。

      【讨论】:

      • 我假设这不会比线程方法快多少。虽然它确实消除了争夺 HDD 访问的多个线程,但它仍然有很多随机 I/O。
      • 不超过这个:stackoverflow.com/a/17483103/564226 - 并且可以通过使用缓冲区打开文件或使用显式 BufferedStream 来改进它。顺便说一句,我的印象是 OP 的线程方法没有读取下一行,而是一直读取到某个行号。
      • @jeffrson IIRC StreamReader/Writer 默认缓冲。
      猜你喜欢
      • 1970-01-01
      • 2015-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多