如何使用Microsoft.IO.RecyclableMemoryStream将字节数组生成ZIP文件?
问题:RecyclableMemoryStream与ZipArchive配合的归档异常问题
我需要将字节数组保存为ZIP压缩文件,由于Azure共享服务器实例内存限制,使用Microsoft.IO.RecyclableMemoryStream避免内存不足,但它和ZipArchive配合出现了以下问题:
1. 生成损坏归档的尝试
第一次尝试的代码会生成无法打开的未完成归档:
var data = Encoding.UTF8.GetBytes("This is a test string"); byte[] compressedBytes = default; var manager = new RecyclableMemoryStreamManager(); using var originalStream = manager.GetStream("Raw", data); using var compressedStream = manager.GetStream("Compressed"); using var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false); var zipEntry = zipArchive.CreateEntry("test.txt"); using var zipEntryStream = zipEntry.Open(); originalStream.CopyTo(zipEntryStream); // 以下操作无效,归档仍损坏 zipEntryStream.Flush(); zipEntryStream.Close(); compressedBytes = compressedStream.ToArray(); // 结果:文件无法打开,提示"Unexpected end of archive" // 注:这里直接返回字节仅用于测试,实际控制器中不会这么做 return File(compressedBytes, "application/zip", "test.zip");
结果是生成的ZIP文件无法打开,提示*"Unexpected end of archive"*。
2. 释放ZipArchive后无法访问流的尝试
为了让ZipArchive完成归档,将其放在独立的using块中,但此时底层的RecyclableMemoryStream被释放,无法获取字节:
using var originalStream = manager.GetStream("Raw", data); using var compressedStream = manager.GetStream("Compressed"); // 独立using块确保ZipArchive及时释放以完成归档 using (var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false)) { var zipEntry = zipArchive.CreateEntry("test.txt"); using var zipEntryStream = zipEntry.Open(); originalStream.CopyTo(zipEntryStream); } // 抛出异常:Cannot access a disposed object. compressedBytes = compressedStream.ToArray();
此时归档完成,但抛出*"Cannot access a disposed object"*异常,无法获取压缩后的字节数组。
3. 使用传统MemoryStream可正常工作
只有使用传统MemoryStream的方式能正常生成可打开的ZIP文件:
var manager = new RecyclableMemoryStreamManager(); using var originalStream = manager.GetStream("Raw", data); using var compressedStream = new MemoryStream(); // 独立using块确保ZipArchive完成归档 using (var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false)) { var zipEntry = zipArchive.CreateEntry("test.txt"); using var zipEntryStream = zipEntry.Open(); originalStream.CopyTo(zipEntryStream); } compressedBytes = compressedStream.ToArray();
解决方案:设置ZipArchive的leaveOpen参数为true
问题的核心是ZipArchive的第三个构造参数leaveOpen,将其设为true,就能让ZipArchive在释放时不关闭底层的RecyclableMemoryStream,同时完成归档的收尾工作。
修正后的代码:
var data = Encoding.UTF8.GetBytes("This is a test string"); byte[] compressedBytes = default; var manager = new RecyclableMemoryStreamManager(); using var originalStream = manager.GetStream("Raw", data); using var compressedStream = manager.GetStream("Compressed"); // 将leaveOpen设为true,避免ZipArchive释放时关闭底层流 using (var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, true)) { var zipEntry = zipArchive.CreateEntry("test.txt"); using var zipEntryStream = zipEntry.Open(); originalStream.CopyTo(zipEntryStream); } // 此时compressedStream未被释放,可正常获取字节数组 compressedBytes = compressedStream.ToArray(); return File(compressedBytes, "application/zip", "test.zip");
原理说明
leaveOpen: true是关键:它告诉ZipArchive,在自身被释放时不要关闭传入的底层流,这样RecyclableMemoryStream就能继续被使用来读取压缩后的字节。ZipArchive在释放时会自动完成归档的收尾工作(比如写入ZIP目录结构、校验信息),这解决了第一个尝试中归档损坏的问题。
备选方案:使用第三方压缩库
如果对ZipArchive的行为仍有顾虑,可以考虑使用第三方压缩库,比如SharpZipLib或DotNetZip,这些库对自定义流的兼容性更好,且提供更灵活的归档控制选项。
内容的提问来源于stack exchange,提问作者JustAMartin
相关产品推荐
相关产品推荐

