从字节(具有任意编码的文本)在内存中创建zip文件

7
我正在开发的应用程序需要将XML文件压缩成ZIP文件,并通过HTTP请求发送到Web服务。由于我不需要保留ZIP文件,所以我只在内存中执行压缩。Web服务拒绝我的请求,因为ZIP文件显然是格式错误的。
我知道在这个问题中有一个解决方案,它运行得非常完美,但它使用了StreamWriter。我对那个解决方案的问题在于StreamWriter需要一个编码或者假定使用UTF-8,而我不需要知道XML文件的编码。我只需要从这些文件中读取字节,并将它们存储在ZIP文件中,无论它们使用什么编码。
因此,明确一下,这个问题与编码无关,因为我不需要将字节转换成文本或反之亦然。我只需要压缩byte[]
我正在使用下面的代码测试我的ZIP文件是否格式错误:
static void Main(string[] args)
{
    Encoding encoding = Encoding.GetEncoding("ISO-8859-1");

    string xmlDeclaration = "<?xml version=\"1.0\" encoding=\"" + encoding.WebName.ToUpperInvariant() + "\"?>";
    string xmlBody = "<Test>ª!\"·$%/()=?¿\\|@#~€¬'¡º</Test>";
    string xmlContent = xmlDeclaration + xmlBody;
    byte[] bytes = encoding.GetBytes(xmlContent);
    string fileName = "test.xml";
    string zipPath = @"C:\Users\dgarcia\test.zip";

    Test(bytes, fileName, zipPath);
}

static void Test(byte[] bytes, string fileName, string zipPath)
{
    byte[] zipBytes;

    using (var memoryStream = new MemoryStream())
    using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: false))
    {
        var zipEntry = zipArchive.CreateEntry(fileName);
        using (Stream entryStream = zipEntry.Open())
        {
            entryStream.Write(bytes, 0, bytes.Length);
        }

        //Edit: as the accepted answer states, the problem is here, because i'm reading from the memoryStream before disposing the zipArchive.
        zipBytes = memoryStream.ToArray();
    }

    using (var fileStream = new FileStream(zipPath, FileMode.OpenOrCreate))
    {
        fileStream.Write(zipBytes, 0, zipBytes.Length);
    }
}

如果我尝试打开该文件,会出现“意外的文件结尾”错误。因此,很明显,Web服务正确地报告了一个格式不正确���zip文件。我已经尝试过以下方法:
  • 刷新 entryStream
  • 关闭 entryStream
  • 同时刷新和关闭 entryStream
请注意,如果我直接从 fileStream 打开 zipArchive ,则zip文件将无错误地形成。但是, fileStream 仅作为测试存在,我需要在内存中创建zip文件。

不确定是否重要,但如果另一端关心编码并假设为UTF8,则您的测试输入将会损坏(€)。C#中的字符串始终为UTF16,那么为什么不直接以UTF8编写呢? - Alex K.
如果我没错的话,另一端应该关心编码,并且应该使用XML声明所指定的确切编码。因此,在这种情况下,所有符号都将使用ISO-8859-1进行编码,而另一端应该同样使用ISO-8859-1进行解码。 - Daniel García Rubio
2个回答

12
你正在尝试过早从 MemoryStream 获取字节,因为ZipArchive并未全部写入。相反,请按照以下方式进行操作:
using (var memoryStream = new MemoryStream()) {
    // note "leaveOpen" true, to not dispose memoryStream too early
    using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) {
        var zipEntry = zipArchive.CreateEntry(fileName);
        using (Stream entryStream = zipEntry.Open()) {
            entryStream.Write(bytes, 0, bytes.Length);
        }                    
    }
    // now, after zipArchive is disposed - all is written to memory stream
    zipBytes = memoryStream.ToArray();
}

1
这正是我的代码无法运行的原因。谢谢。 - Daniel García Rubio
1
在最后是否不需要执行 Flush 操作?我不确定,有些流需要,有些则不需要。 - Joseph Katzman

-1
如果你使用内存流来加载文本,你可以控制编码类型,并且它可以跨WCF服务工作。这是我目前正在使用的实现方式,它可以在我的WCF服务上正常工作。
    private byte[] Zip(string text)
    {
        var bytes = Encoding.UTF8.GetBytes(text);

        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
            {
                CopyTo(msi, gs);
            }

            return mso.ToArray();
        }
    }

    private string Unzip(byte[] bytes)
    {
        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                CopyTo(gs, mso);
            }

            return Encoding.UTF8.GetString(mso.ToArray());
        }
    }

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