通过块读取非常大的文件而不是逐行读取

时间:2014-01-15 11:55:03

标签: c# file

我想读取一个CSV文件,其大小可达数百GB甚至TB。我有一个限制,我只能以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不是一个可接受的解决方案)。为什么?因为我只是在寻找另一种解决方案......

2 个答案:

答案 0 :(得分:5)

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

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,我认为您不需要这个。

此外,问题是如果您的整个数据只包含一行,最终会尝试将整个数据读入内存。

答案 1 :(得分:4)

which can be at a size of hundreds of GBs and even TB

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

一些优点:

  • 理想的是访问磁盘上的数据文件而不执行文件I / O操作和缓冲文件的内容。这在处理大型数据文件时效果很好。

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

所以尝试一下,你会注意到差异,因为内存和硬盘之间的交换是一项耗时的操作