我正在构建一个ASP.NET核心应用程序,该应用程序需要处理大型文件上传 - 高达200GB。我的目标是将这些文件写入磁盘并同时捕获MD5哈希。
我已经经历了自己的方法,并创建了自己的方法,以从HTTP客户端请求中识别文件流,如将大型文件与流有关的上传时所概述。一旦找到流,我将使用以下代码写入磁盘并创建MD5哈希。
// removed the curly brackets from using statements for readability on Stack Overflow
var md5 = MD5.Create();
using (var targetStream = File.OpenWrite(pathAndFileName))
using (var cryptoStream = new CryptoStream(targetStream, md5, CryptoStreamMode.Write))
using (var sourceStream = fileNameAndStream.FileStream)
{
await sourceStream.CopyToAsync(cryptoStream);
}
var hash = md5.Hash;
md5.Dispose();
令人敬畏的是上述作品(创建和哈希生成的文件(。我不太了解它的工作原理:
-
cryptoStream
是否被复制到targetStream
? -
cryptoStream
是将字节保存在内存中还是只是在读取它们时读取它们? -
cryptoStream
和targetStream
既异步发生? - 或者是
cryptoStream
的异步副本,并同步写入targetStream
?
我很高兴这起作用,但是在没有完全理解的情况下,我担心我介绍了一些邪恶的东西。
它的工作原理:
1(CopyToAsync
分配指定尺寸的字节缓冲区(或使用默认大小,如果您使用的过载尺寸为"有问题"(。然后,它在源流上调用ReadAsync
以填充该缓冲区,然后在目标流上调用WriteAsync
以将该缓冲区写入目标流。重复直到编写所有数据。因此,此操作将小字节数组(缓冲区(保存在内存中。阅读和写作是异步的(如果源目标流支持(。
2(在写入模式中的CryptoStream
这样可以使用:当您写入它时,它需要您编写的缓冲区(这是上面讨论的缓冲区相同的(,并将其馈送到您传递给它的ICryptoTransform
ImpartimentAiton(在这种情况下-MD5
(。变换可能需要在特定尺寸的块中进行处理(由ICryptoTransform.InputBlockSize
属性确定(。在那种情况下,CryptoStream
可能会缓存您写入的数据,直到有特定大小的完整块为止。这不是问题,因为这些块通常很小(比CopyAsync
的合理缓冲尺寸小得多(。然后,它将这些块将这些块传递给ICryptoTransform.TransformBlock
,然后接收输出(另一个字节数组(。这个过程是同步的,因为无论如何,这里没有什么可以异步的。
3(块被ICryptoTransform
转换后 - 此块将其写入输出流(在这种情况下为targetStream
(异步(使用WriteAsync
(。因此,CryptoStream
的内存消耗也很小,并且与目标trasform输入和输出块大小相关。
4(MD5
ICryptoTransform
的实现使用传递的块来连续计算哈希,因为该算法不需要立即存在完整的数据来计算哈希,它可以按块计算它。然后,它输出与输入上接收到的完全相同的块,因此没有进行转换。这意味着MD5的TransformBlock
只是在内部更新哈希时返回输入。
总结并回答您的问题:
- 加密流只能将小缓冲区保存到缓冲区数据以转换输入块大小,它将转换的数据尽快写入输出流。它不保留整个数据的副本。
- 在加密流本身中没有发生IO工作,它仅执行CPU绑定工作(转换(,这是同步发生的。但是,当您写入加密流时 - 它会写入目标流 - 这确实是异步发生的。
旁注 - 要真正利用异步文件io-您需要使用"异步"选项初始化文件流,例如:
new FileStream(pathAndFileName, FileMode.Create, FileAccess.Write, FileShare.None,
4096, FileOptions.Asynchronous)
否则,即使使用WriteAsync
,您的目标流也将是同步的。