ZipArchive创建的ZIP文件无效。

61

我正在尝试使用 System.IO.Compression.ZipArchive 类创建一个只包含一个条目的新 ZIP 包,并将其保存到文件中。

using (MemoryStream zipStream = new MemoryStream())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine(
                "Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }

然后我在WinRT中将ZIP保存到文件中:

        var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync("test.zip", CreationCollisionOption.ReplaceExisting);
        zipStream.Position = 0;
        using (Stream s = await file.OpenStreamForWriteAsync())
        {
            zipStream.CopyTo(s);
        }

或在普通的.NET 4.5中:

        using (FileStream fs = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
        {
            zipStream.Position = 0;
            zipStream.CopyTo(fs);
        }

然而,我无法在Windows Explorer、WinRAR等软件中打开生成的文件。(我已经检查了生成的文件大小是否与zipStream的长度匹配,所以流本身已经正确地保存到文件中。)
我是做错了什么还是ZipArchive类存在问题?


1
如果您要添加二进制数据而不是字符串,这是一个很好的示例:https://dev59.com/cqnka4cB1Zd3GeqPLUQw - John Gilmer
7个回答

129

我发现了在我的代码中显而易见的错误。ZipArchive必须被释放(disposed)才能将其内容写入其底层流中。因此,在ZipArchive的using块结束后,我不得不保存流到文件中。
同样重要的是将其构造函数的leaveOpen参数设置为true,以使其不关闭底层流。因此,这里是完整的工作解决方案:

using (MemoryStream zipStream = new MemoryStream())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine(
                "Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }
    }

    var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(
        "test.zip",
        CreationCollisionOption.ReplaceExisting);

    zipStream.Position = 0;
    using (Stream s = await file.OpenStreamForWriteAsync())
    {
        zipStream.CopyTo(s);
    }
}

我收到了错误信息:“当前上下文中不存在'Windows'的名称”。我该如何纠正这个问题? - Michael Murphy
1
迈克尔,这是一个构建错误,对吗?你是在开发Windows Store应用还是普通的.NET应用?在普通的.NET应用中,你应该使用FileStream类,而不是Windows.Storage命名空间中的存储类。你可以在问题中看到一个示例。 - Mark Vincze
谢谢@MarkVincze,这对我来说完全不明显。你救了我一命。 - The Senator
我会争辩说这一点根本不明显!它没有任何明显的提示或关闭按钮来表明它有这个功能。依赖于垃圾处理来实现功能是令人厌恶的设计。但还是谢谢你的帖子,救了我的一命。 - Adam Hardy

5
// Create file "archive.zip" in current directory use it as destination for ZIP archive
using (var zipArchive = new ZipArchive(File.OpenWrite("archive.zip"), 
ZipArchiveMode.Create))
{
    // Create entry inside ZIP archive with name "test.txt"
    using (var entry = zipArchive.CreateEntry("test.txt").Open())
    {
        // Copy content from current directory file "test.txt" into created ZIP entry 
        using (var file = File.OpenRead("test.txt"))
        {
            file.CopyTo(entry);
        }
    }
}    

结果,您将得到一个名为“archive.zip”的压缩文件,其中包含单个条目文件“test.txt”。

您需要使用以下级联的using来避免与已处置资源的任何交互。


请为您的代码添加说明,并解释它是如何解决问题的。 - Douglas Gaskell
@DouglasGaskell完成。可能上面的代码没有直接回答问题,但展示了如何使用ZipArchive压缩文件。 - Andrew Dragan

4

在所有的流对象上,您必须从开头倒回流,以便其他使用.Seek方法的应用程序能够正确地读取它们。

例如:

zipStream.Seek(0, SeekOrigin.Begin);

1
这并没有帮助。这就是为什么我将Position属性设置为0,我猜这可以达到相同的效果。 - Mark Vincze
压缩库必须有自己的SaveAs方法,请尝试查看,此外在项目页面上还有各种不同的保存方案文档。 - Freeman
1
嗯,ZipArchive类(http://msdn.microsoft.com/en-us/library/system.io.compression.ziparchive.aspx)没有SaveAs方法。你所说的“项目页面”是什么意思? - Mark Vincze
抱歉,我以为这是第三方库。 - Freeman
@Freeman 这在我的情况下起到了作用。谢谢! - Leniel Maccaferri

2
在我的情况下,问题是当zip文件任务完成时我没有处理好"zipArchive"。即使我刷新和关闭了所有流。
因此,建议使用上面的答案中提到的using
using (var zipArchive = new ZipArchive(File.OpenWrite("archive.zip"), 
ZipArchiveMode.Create))

在任务结束时处理变量,或者释放变量。保留HTML格式,不进行解释。
zipArchive.Dispose();

1
您可以按照相同的思路,只是使用文件流作为源代码并反转顺序。我做了下面的表单,然后文件正常打开:
string fileFormat = ".zip"; // any format
string filename = "teste" + fileformat;

StorageFile zipFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename,CreationCollisionOption.ReplaceExisting);

using (Stream zipStream = await zipFile.OpenStreamForWriteAsync()){

   using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)){
        //Include your content here
   }
}

0

完整的代码如下:

var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync("test.zip",CreationCollisionOption.ReplaceExisting);
using (Stream zipStream = await zipFile.OpenStreamForWriteAsync())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine("Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }
    }   
}

0
使用没有花括号的指令时要小心(C# >8.0):
static byte[] CompressAsZip(string fileName, Stream fileStram)
{
    using var memoryStream = new MemoryStream();
    using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false);
    using var entryStream = archive.CreateEntry(fileName).Open();

    fileStram.CopyTo(entryStream);

    archive.Dispose(); //<- Necessary to close the archive correctly
    return memoryStream.ToArray();
}

使用带有花括号的using语句,archive.Dispose将在作用域结束时隐式调用。 此外,由于您正在使用C# >8.0,我建议使用异步实现此类型,能够归档多个文件并可以取消操作。
static async Task<byte[]> CompressAsZipAsync(
    IAsyncEnumerable<(string FileName, Stream FileStream)> files,
    bool disposeStreamsAfterCompression = false,
    CancellationToken cancellationToken = default)
{
    using var memoryStream = new MemoryStream();
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false))
    {
        await foreach (var (FileName, FileStream) in files.WithCancellation(cancellationToken))
        {
            using var entryStream = archive.CreateEntry(FileName).Open();
            await FileStream.CopyToAsync(entryStream, cancellationToken);

            if (disposeStreamsAfterCompression)
                await FileStream.DisposeAsync();
        }
    }
    return memoryStream.ToArray();
}

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