使用System.IO.Compression在内存中创建ZIP归档文件

248

我正在尝试使用 MemoryStream 创建一个包含简单演示文本文件的 ZIP 存档文件,代码如下:

using (var memoryStream = new MemoryStream())
using (var archive = new ZipArchive(memoryStream , ZipArchiveMode.Create))
{
    var demoFile = archive.CreateEntry("foo.txt");

    using (var entryStream = demoFile.Open())
    using (var streamWriter = new StreamWriter(entryStream))
    {
        streamWriter.Write("Bar!");
    }

    using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
    {
        stream.CopyTo(fileStream);
    }
}

如果我运行这段代码,归档文件本身被创建了,但是foo.txt并没有被创建。

然而,如果我直接用文件流替换MemoryStream,那么归档将会被正确创建:

using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
using (var archive = new ZipArchive(fileStream, FileMode.Create))
{
    // ...
}

是否可以使用MemoryStream创建ZIP归档文件而不使用FileStream


请注意:使用.ZipArchive至少需要.NET 4.5。请参阅Rick Strahl的.NET 4.5是.NET 4.0的就地替换~~另请参阅我在“System.IO.Compression”命名空间中找不到“ZipFile”类 - gerryLowry
如果您想要一个使用二进制数据而不是字符串的示例,这里有一个很好的解决方案:https://dev59.com/cqnka4cB1Zd3GeqPLUQw - John Gilmer
12个回答

412

多亏了ZipArchive创建无效的ZIP文件,我得到了:

using (var memoryStream = new MemoryStream())
{
   using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
   {
      var demoFile = archive.CreateEntry("foo.txt");

      using (var entryStream = demoFile.Open())
      using (var streamWriter = new StreamWriter(entryStream))
      {
         streamWriter.Write("Bar!");
      }
   }

   using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
   {
      memoryStream.Seek(0, SeekOrigin.Begin);
      memoryStream.CopyTo(fileStream);
   }
}

这表明我们需要在使用ZipArchive之前调用Dispose,正如Amir所建议的那样,这很可能是因为它会将最终字节(如校验和)写入归档文件中以使其完整。但是为了不关闭流,以便我们可以在之后重复使用它,您需要将true作为第三个参数传递给ZipArchive


1
适用于桌面和Web API的好解决方案。 - Hien Nguyen

123

另一种不写入任何文件的压缩版本。

string fileName = "export_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".xlsx";
byte[] fileBytes = here is your file in bytes
byte[] compressedBytes;
string fileNameZip = "Export_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".zip";

using (var outStream = new MemoryStream())
{
    using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
    {
        var fileInArchive = archive.CreateEntry(fileName, CompressionLevel.Optimal);
        using (var entryStream = fileInArchive.Open())
        using (var fileToCompressStream = new MemoryStream(fileBytes))
        {
            fileToCompressStream.CopyTo(entryStream);
        }
    }
    compressedBytes = outStream.ToArray();
}

1
简单而干净。使用MemoryStream和Azure文件存储blob对我很有效。 - Ken Forslund
1
一个优美的答案 - RedRose

21
将流的位置设置为0,然后将其复制到zip流中。
using (var memoryStream = new MemoryStream())
{
   using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
   {
         var demoFile = archive.CreateEntry("foo.txt");

         using (var entryStream = demoFile.Open())
         using (var streamWriter = new StreamWriter(entryStream))
         {
             streamWriter.Write("Bar!");
          }
   }

    using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
    {
         memoryStream.Position=0;
         memoryStream.WriteTo(fileStream);
    }
 }

感谢提供简单明了的解决方案。赞! - shaishav shukla

16
MVC的工作解决方案
public ActionResult Index()
{
    string fileName = "test.pdf";
    string fileName1 = "test.vsix";
    string fileNameZip = "Export_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".zip";

    byte[] fileBytes = System.IO.File.ReadAllBytes(@"C:\test\test.pdf");
    byte[] fileBytes1 = System.IO.File.ReadAllBytes(@"C:\test\test.vsix");
    byte[] compressedBytes;
    using (var outStream = new MemoryStream())
    {
        using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
        {
            var fileInArchive = archive.CreateEntry(fileName, CompressionLevel.Optimal);
            using (var entryStream = fileInArchive.Open())
            using (var fileToCompressStream = new MemoryStream(fileBytes))
            {
                fileToCompressStream.CopyTo(entryStream);
            }

            var fileInArchive1 = archive.CreateEntry(fileName1, CompressionLevel.Optimal);
            using (var entryStream = fileInArchive1.Open())
            using (var fileToCompressStream = new MemoryStream(fileBytes1))
            {
                fileToCompressStream.CopyTo(entryStream);
            }
        }
        compressedBytes = outStream.ToArray();
    }
    return File(compressedBytes, "application/zip", fileNameZip);
}

4
Controller.File方法有一个重载,可以接受Stream。使用它来避免在内存中创建另一个ZIP文件的副本。 - Martin Prikryl
1
天啊,这是我在谷歌上搜了一个小时后得到的第一个直接的答案。谢谢! - Darth Scitus
1
这个解决方案完美运行!谢谢!你甚至不需要移动字节数组(byte[])。只需使用MemoryStream,别忘了在将结果返回之前将其归零(.Seek(0, SeekOrigin.Begin);)。 - Tolbxela
可以确认这在使用带有WebApi的.NET Core 5.*中对我有效。 - Michael K
也适用于 .net 7。 - Luis Lopez
显示剩余2条评论

6
你需要完成对内存流的写入,然后再读取缓冲区。
        using (var memoryStream = new MemoryStream())
        {
            using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create))
            {
                var demoFile = archive.CreateEntry("foo.txt");

                using (var entryStream = demoFile.Open())
                using (var streamWriter = new StreamWriter(entryStream))
                {
                    streamWriter.Write("Bar!");
                }
            }

            using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
            {
                var bytes = memoryStream.GetBuffer();
                fileStream.Write(bytes,0,bytes.Length );
            }
        }

3
using System;
using System.IO;
using System.IO.Compression;

namespace ConsoleApplication
{
    class Program`enter code here`
    {
        static void Main(string[] args)
        {
            using (FileStream zipToOpen = new FileStream(@"c:\users\exampleuser\release.zip", FileMode.Open))
            {
                using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
                {
                    ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
                    using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
                    {
                            writer.WriteLine("Information about this package.");
                            writer.WriteLine("========================");
                    }
                }
            }
        }
    }
}

3
我来晚了,但有些情况下您不能访问 ZipArchive 构造函数来设置 leaveOpen 参数,也不想将 ZIP 文件写入磁盘。在我的情况下,我在内部使用的 AsiceArchive 类创建了一个 ZipArchive,但没有将 leaveOpen 设置为 true。
我创建了一个 Stream 的子类,将所有调用委托给内部流(使用 ReSharper 十分方便)。这个类不可释放,所以当 ZipArchive 被释放时,内部流不会受到任何影响。
public class NondisposingStreamWrapper : Stream
{
    private readonly Stream _streamImplementation;

    public NondisposingStreamWrapper(Stream inner) => _streamImplementation = inner;

    public override void Flush() => _streamImplementation.Flush();

    public override int Read(byte[] buffer, int offset, int count) => _streamImplementation.Read(buffer, offset, count);

    public override long Seek(long offset, SeekOrigin origin) => _streamImplementation.Seek(offset, origin);

    public override void SetLength(long value) => _streamImplementation.SetLength(value);

    public override void Write(byte[] buffer, int offset, int count) => _streamImplementation.Write(buffer, offset, count);

    public override bool CanRead => _streamImplementation.CanRead;

    public override bool CanSeek => _streamImplementation.CanSeek;

    public override bool CanWrite => _streamImplementation.CanWrite;

    public override long Length => _streamImplementation.Length;

    public override long Position
    {
        get => _streamImplementation.Position;
        set => _streamImplementation.Position = value;
    }
}

使用方法如下:

using var memoryStream = new MemoryStream();
var output = new NondisposingStreamWrapper(memoryStream);

using (var archive = new ZipArchive(output, ZipArchiveMode.Create))
{
    // add entries to archive
}

memoryStream.Flush();
memoryStream.Position = 0;

// write to file just for testing purposes
File.WriteAllBytes("out.zip", memoryStream.ToArray());

3

返回包含zip文件的流的函数

public static Stream ZipGenerator(List<string> files)
    {
        ZipArchiveEntry fileInArchive;
        Stream entryStream;
        int i = 0;
        List<byte[]> byteArray = new List<byte[]>();

        foreach (var file in files)
        {
            byteArray.Add(File.ReadAllBytes(file));
        }

        var outStream = new MemoryStream();

        using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
        {
            foreach (var file in files)
            {
                fileInArchive=(archive.CreateEntry(Path.GetFileName(file), CompressionLevel.Optimal));

                using (entryStream = fileInArchive.Open())
                {
                        using (var fileToCompressStream = new MemoryStream(byteArray[i]))
                        {
                            fileToCompressStream.CopyTo(entryStream);
                        }
                        i++;
                }
            }
        }
        outStream.Position = 0;
        return outStream;
    }

如果需要,将文件压缩为 zip 格式并写入文件流中。

using (var fileStream = new FileStream(@"D:\Tools\DBExtractor\DBExtractor\bin\Debug\test.zip", FileMode.Create))
{
   outStream.Position = 0;
   outStream.WriteTo(fileStream);
}

`


将所有文件先读入内存是一种巨大的内存浪费。你最终会使得所有文件都在内存中出现两次,一次在byteArray中,另一次在ZipArchive里。更不用说你根本不需要把文件加载到内存中。使用流式传输,就像其他现有答案所示。 - Martin Prikryl
@MartinPrikryl 问题是如何将zip文件写入内存。这就是我使用内存的原因。当然,最好的方法是写入本地。 - Arun C S
我知道这个问题是关于什么的。我的评论重点是,你在内存中创建ZIP文件的实现非常低效。其他答案中的实现更好。 - Martin Prikryl
1
与 StackOverflow 上的任何代码一样,此代码必须重构以适应您的需求。在尝试其他答案中概述的解决方案无效后,我能够快速从这个答案中获得一个解决方案,然后重构代码以消除冗余。 - Michael Earls

0
这是将实体转换为XML文件并压缩的方法:
private  void downloadFile(EntityXML xml) {

string nameDownloadXml = "File_1.xml";
string nameDownloadZip = "File_1.zip";

var serializer = new XmlSerializer(typeof(EntityXML));

Response.Clear();
Response.ClearContent();
Response.ClearHeaders();
Response.AddHeader("content-disposition", "attachment;filename=" + nameDownloadZip);

using (var memoryStream = new MemoryStream())
{
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
    {
        var demoFile = archive.CreateEntry(nameDownloadXml);
        using (var entryStream = demoFile.Open())
        using (StreamWriter writer = new StreamWriter(entryStream, System.Text.Encoding.UTF8))
        {
            serializer.Serialize(writer, xml);
        }
    }

    using (var fileStream = Response.OutputStream)
    {
        memoryStream.Seek(0, SeekOrigin.Begin);
        memoryStream.CopyTo(fileStream);
    }
}

Response.End();

}


0

对我来说,这样就可以了:

using (var memoryStream = new MemoryStream())
{
   using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
   {
      var file = archive.CreateEntry("file.json");
      using var entryStream = file.Open();
      using var streamWriter = new StreamWriter(entryStream);
      streamWriter.WriteLine(someJsonLine);
   }
}

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