【问题标题】:Read a very large file by chunks and not line-by-line按块而不是逐行读取一个非常大的文件
【发布时间】:2021-03-25 20:12:53
【问题描述】:

我想读取一个大小为数百 GB 甚至 TB 的 CSV 文件。我有一个限制,我只能读取 32MB 的文件。我对这个问题的解决方案,它不仅工作有点慢,而且还可能在中间断线。

我想问你是否知道更好的解决方案:

const int MAX_BUFFER = 33554432; //32MB
byte[] buffer = new byte[MAX_BUFFER];
int bytesRead;

using (FileStream fs = File.Open(filePath, FileMode.Open, FileAccess.Read))
using (BufferedStream bs = new BufferedStream(fs))
{
    string line;
    bool stop = false;
    while ((bytesRead = bs.Read(buffer, 0, MAX_BUFFER)) != 0) //reading only 32mb chunks at a time
    {
        var stream = new StreamReader(new MemoryStream(buffer));
        while ((line = stream.ReadLine()) != null)
        {
            //process line
        }

    }
}

请不要回复逐行读取文件的解决方案(例如,File.ReadLines 不是可接受的解决方案)。为什么?因为我只是在寻找另一种解决方案...

【问题讨论】:

  • 为什么它不是一个可接受的解决方案?请记住,在您阅读之前,框架不会涉及您正在阅读的文件的内容。

标签: c# file


【解决方案1】:

您的解决方案的问题是您在每次迭代中重新创建流。试试这个版本:

const int MAX_BUFFER = 33554432; //32MB
byte[] buffer = new byte[MAX_BUFFER];
int bytesRead;
StringBuilder currentLine = new StringBuilder();

using (FileStream fs = File.Open(filePath, FileMode.Open, FileAccess.Read))
using (BufferedStream bs = new BufferedStream(fs))
{
    string line;
    bool stop = false;
    var memoryStream = new MemoryStream(buffer);
    var stream = new StreamReader(memoryStream);
    while ((bytesRead = bs.Read(buffer, 0, MAX_BUFFER)) != 0)
    {
        memoryStream.Seek(0, SeekOrigin.Begin);

        while (!stream.EndOfStream)
        {
            line = ReadLineWithAccumulation(stream, currentLine);

            if (line != null)
            {
                //process line
            }
        }
    }
}

private string ReadLineWithAccumulation(StreamReader stream, StringBuilder currentLine)
{
    while (stream.Read(buffer, 0, 1) > 0)
    {
        if (charBuffer [0].Equals('\n'))
        {
            string result = currentLine.ToString();
            currentLine.Clear();

            if (result.Last() == '\r') //remove if newlines are single character
            {
                result = result.Substring(0, result.Length - 1);
            }

            return result;
        }
        else
        {
            currentLine.Append(charBuffer [0]);
        }
    }

    return null;  //line not complete yet
}

private char[] charBuffer = new char[1];

注意:如果换行符有两个字符长并且您需要在结果中包含换行符,则需要进行一些调整。最坏的情况是换行对 "\r\n" 分成两个块。但是,由于您使用的是ReadLine,因此我认为您不需要它。

另外,问题是如果你的整个数据只包含一行,这最终会试图将整个数据读入内存。

【讨论】:

    【解决方案2】:
    which can be at a size of hundreds of GBs and even TB
    

    对于大文件处理,推荐的最合适的类是MemoryMappedFileClass

    一些优点:

    • 在不执行文件 I/O 操作和缓冲文件内容的情况下访问磁盘上的数据文件是理想的选择。这在处理大型数据文件时非常有用。

    • 您可以使用内存映射文件来允许在同一台机器上运行的多个进程相互共享数据。

    试试吧,你会注意到内存和硬盘之间的交换是一个耗时的操作的区别

    【讨论】:

    • 你能帮我理解MSDN中的例子吗?我不明白他们在创建视图访问器后在做什么。我还查看了msdn.microsoft.com/en-us/library/…(由于某种原因似乎无法使其成为正确的链接),我并没有真正理解他们在示例中使用了哪种 Read 方法/
    猜你喜欢
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    • 2018-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多