如何在不将整个文件加载到内存中的情况下读取/流式传输文件?

20

如何读取任意文件并“分段”处理它(即逐字节或其他块大小以获得最佳读取性能),而不将整个文件加载到内存中? 处理的示例是生成文件的MD5哈希,尽管答案适用于任何操作。

我想要拥有或编写此代码,但如果我可以获得现有的代码那就太好了。(c#)


2
看,真正的答案是:“System.IO.FileStream 不会将文件加载到内存中。” - Vercas
5个回答

31

这是一个读取文件的例子,每次以1KB为单位读取文件块,而不需要将整个文件内容加载到内存中:

const int chunkSize = 1024; // read the file by chunks of 1KB
using (var file = File.OpenRead("foo.dat"))
{
    int bytesRead;
    var buffer = new byte[chunkSize];
    while ((bytesRead = file.Read(buffer, 0, buffer.Length)) > 0)
    {
        // TODO: Process bytesRead number of bytes from the buffer
        // not the entire buffer as the size of the buffer is 1KB
        // whereas the actual number of bytes that are read are 
        // stored in the bytesRead integer.
    }
}

请说明为什么这段代码没有完全将文件读入内存。另外,请解释一下您的TODO部分。 - Matthew
4
这会将1KB(或chunkSize字节)加载到内存中。编辑:他也指的是并非整个“buffer”都被写入!只有从索引0到索引“bytesRead - 1”的字节被写入。 - Vercas
@Darin - 忽略我在第一条评论中的问题。我发现由于file.Read的结果,只有指定数量的字节被读取。 - Howiecamp
2
@Darin 我尝试了这段代码,但它无法正确读取最后一块。如果最后一块小于“chunkSize”,它会在缓冲区中保留垃圾值。 - Mujeeb
@Mujeeb,你需要读取bytesRead的长度,而不是整个buffer的长度,例如fsFileStream.Write(buffer, 0, bytesRead) - antikbd

11

System.IO.FileStream不会将文件加载到内存中。
该流可寻址,并且MD5哈希算法也不必将流(文件)加载到内存中。

请用文件路径替换file_path

byte[] hash = null;

using (var stream = new FileStream(file_path, FileMode.Open))
{
    using (var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider())
    {
        hash = md5.ComputeHash(stream);
    }
}

在这里,你的MD5哈希将被存储在hash变量中。


对于未来的堆栈开发者而言,你只需要一个using语句——如果我没记错的话,它们可以被组合在一起。 - Richard Barker

4
   int fullfilesize = 0;// full size of file
    int DefaultReadValue = 10485760; //read 10 mb at a time
    int toRead = 10485760;
    int position =0;

  //  int 
 //   byte[] ByteReadFirst = new byte[10485760];

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        using (var fs = new FileStream(@"filepath", FileMode.Open, FileAccess.Read))
        {
            using (MemoryStream requestStream = new MemoryStream())
            {


                fs.Position = position;

                if (fs.Position >= fullfilesize)
                {
                    MessageBox.Show(" all done");
                    return;
                }
                System.Diagnostics.Debug.WriteLine("file position" + fs.Position);

                if (fullfilesize-position < toRead)
                {
                    toRead = fullfilesize - position;
                    MessageBox.Show("last time");
                }
                System.Diagnostics.Debug.WriteLine("toread" + toRead);
                int    bytesRead;
                byte[] buffer = new byte[toRead];
                int offset = 0;
                position += toRead;
                while (toRead > 0 && (bytesRead = fs.Read(buffer, offset, toRead)) > 0)
                {
                    toRead -= bytesRead;
                    offset += bytesRead;
                }

                toRead = DefaultReadValue;


            }
        }
    }

按照Darin的方法,此方式将读取10MB的数据块直到文件结束。


尽管在您的示例中不需要MemoryStream,但您是唯一发布了一个设置FileStream Position的示例。这解决了我的问题,我需要将大文件分割并以每个10兆字节块进行传输。点赞! - DragonZero

2
const int MAX_BUFFER = 1024;
byte[] Buffer = new byte[MAX_BUFFER];
int BytesRead;
using (System.IO.FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
    while ((BytesRead = fileStream.Read(Buffer, 0, MAX_BUFFER)) != 0)
    {
        // Process this chunk starting from offset 0 
        // and continuing for bytesRead bytes!
    }

1
const long numberOfBytesToReadPerChunk = 1000;//1KB
using (BinaryReader fileData = new BinaryReader(File.OpenRead(aFullFilePath))
    while (fileData.BaseStream.Position - fileData.BaseStream.Length > 0)
        DoSomethingWithAChunkOfBytes(fileData.ReadBytes(numberOfBytesToReadPerChunk));

据我理解,这里使用的函数(特别是 BinaryReader.ReadBytes)不需要跟踪已读取的字节数。您只需要知道长度和当前位置以进行 while 循环 - 这是流告诉您的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接