如何将文件解压到.NET内存流?

65

我有一些文件(来自第三方),它们被FTP到我们服务器上的一个目录中。我会每隔 'x' 分钟下载并处理它们,效果很好。

现在,有些文件是 .zip 格式的。这意味着我不能直接处理它们,需要先解压缩。

FTP 协议没有 zip/unzip 的概念 - 因此我需要获取 zip 文件,解压缩它,然后再进行处理。

查看 MSDN zip api,似乎没有办法将文件解压缩到内存流中?

那么,唯一的方法是...

  1. 解压缩到文件 (要使用什么目录?需要一些非常临时的位置...)
  2. 读取文件内容
  3. 删除文件

注意:文件的内容很小 - 大小在 4k <-> 1000k 之间。

6个回答

119

内置Zip压缩支持:

using System.IO;
using System.IO.Compression;
// ^^^ requires a reference to System.IO.Compression.dll
static class Program
{
    const string path = ...
    static void Main()
    {
        using(var file = File.OpenRead(path))
        using(var zip = new ZipArchive(file, ZipArchiveMode.Read))
        {
            foreach(var entry in zip.Entries)
            {
                using(var stream = entry.Open())
                {
                    // do whatever we want with stream
                    // ...
                }
            }
        }
    }
}

通常情况下,您应该避免将其复制到另一个流中 - 只需将其“按原样”使用即可。但是,如果您绝对需要将其放在MemoryStream中,则可以执行以下操作:
using(var ms = new MemoryStream())
{
    stream.CopyTo(ms);
    ms.Position = 0; // rewind
    // do something with ms
}

4
首先,我不确定为什么存在那个类:ZipFile 类的所有方法实际上都是关于 ZipArchive 类的 - 对我来说,它们应该都是 ZipArchive 的静态成员!但更具体地说,因为 OP 谈论的是从现有来源获取数据 - 在这种情况下是 FTP。在这种情况下,你不能保证你有一个文件,但通常可以假设你有一个。因此,展示如何从中获取数据更加可重用和适用于任何上下文,而不仅仅是文件。但当然:你可以在这里使用 ZipFile.OpenRead - Marc Gravell
4
@Uriil 同时,ZipFile需要额外的程序集引用(System.IO.Compression.FileSystem.dll),只是为了避免使用简单的File.OpenRead - 这似乎不值得。 - Marc Gravell
1
仅在 .net 4.5 及更高版本中支持,不支持 XP。 - linquize
3
作为专业人士,我们最好不要支持XP操作系统:这样做会给我们的客户带来风险(因为我们在隐含地认可它)。该操作系统已经被正式宣布为过时。最后一个EOL日期将在大约两周后到来:“2014年4月8日之后,Windows XP的支持和安全更新将不再提供。” - Marc Gravell
2
@JasonBaley 以牺牲预先将所有内容缩小到内存中的成本为代价;利弊 - Marc Gravell
显示剩余4条评论

27
你可以使用 ZipArchiveEntry.Open 方法来获取一个流。
此代码假设压缩文档中有一个文本文件。
using (FileStream fs = new FileStream(path, FileMode.Open))
using (ZipArchive zip = new ZipArchive(fs) )
{
    var entry = zip.Entries.First();

    using (StreamReader sr = new StreamReader(entry.Open()))
    {
        Console.WriteLine(sr.ReadToEnd());
    }
}

3
显而易见的评论:如果数据不是文本,或者数据采用不寻常的编码但缺少字节顺序标记(BOM),那么这将以令人讨厌的方式破裂。 - Marc Gravell
1
@Gusdor 为什么要进行这个编辑? 在我看来,原文更好且更积极,但无论如何,这似乎并不值得编辑。 - Marc Gravell
1
@MarcGravell,我觉得这样做可以让代码更加明确,对于可能不了解省略大括号行为的读者来说更易懂。 - Gusdor
@MarcGravell 是的,我只是添加了 StreamReader 来展示最简单的使用情况。当然,如果你读取的不是文本,那么 StreamReader.ReadToEnd 就不是你要找的东西。(我撤销了 Gusdor 的编辑)。 - dcastro
2
@Gusdor 更易阅读,更容易理解(在我看来),并且可以防止代码向右扩展。代码可读性是一项功能。 - Marc Gravell
显示剩余3条评论

15
using (ZipArchive archive = new ZipArchive(webResponse.GetResponseStream()))
{
     foreach (ZipArchiveEntry entry in archive.Entries)
     {
        Stream s = entry.Open();
        var sr = new StreamReader(s);
        var myStr = sr.ReadToEnd();
     }
} 

1
你需要为 Stream sStreamReader sr 都加上 using 语句,以便自动关闭它们。 - ZeW

10

看起来这就是你需要的:

using (var za = ZipFile.OpenRead(path))
{
    foreach (var entry in za.Entries)
    {
        using (var r = new StreamReader(entry.Open()))
        {
            //your code here
        }
    }
}

0

您可以使用SharpZipLib等多种库来实现此目的。

您可以使用以下代码示例将文件解压缩到MemoryStream中,如其Wiki所示

using ICSharpCode.SharpZipLib.Zip;

// Compresses the supplied memory stream, naming it as zipEntryName, into a zip,
// which is returned as a memory stream or a byte array.
//
public MemoryStream CreateToMemoryStream(MemoryStream memStreamIn, string zipEntryName) {

    MemoryStream outputMemStream = new MemoryStream();
    ZipOutputStream zipStream = new ZipOutputStream(outputMemStream);

    zipStream.SetLevel(3); //0-9, 9 being the highest level of compression

    ZipEntry newEntry = new ZipEntry(zipEntryName);
    newEntry.DateTime = DateTime.Now;

    zipStream.PutNextEntry(newEntry);

    StreamUtils.Copy(memStreamIn, zipStream, new byte[4096]);
    zipStream.CloseEntry();

    zipStream.IsStreamOwner = false;    // False stops the Close also Closing the underlying stream.
    zipStream.Close();          // Must finish the ZipOutputStream before using outputMemStream.

    outputMemStream.Position = 0;
    return outputMemStream;

    // Alternative outputs:
    // ToArray is the cleaner and easiest to use correctly with the penalty of duplicating allocated memory.
    byte[] byteArrayOut = outputMemStream.ToArray();

    // GetBuffer returns a raw buffer raw and so you need to account for the true length yourself.
    byte[] byteArrayOut = outputMemStream.GetBuffer();
    long len = outputMemStream.Length;
}

8
注意:您无需使用外部库 - 压缩支持实际上已经多次出现在BCL中。 - Marc Gravell

0

好的,将上述所有内容结合起来,假设您想以非常简单的方式将名为"file.zip"的压缩文件提取到"C:\temp"文件夹中。(注意:此示例仅针对压缩文本文件进行了测试) 您可能需要对二进制文件进行一些修改。

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

        static void Main(string[] args)
        {
            //Call it like this:
            Unzip("file.zip",@"C:\temp");
        }

        static void Unzip(string sourceZip, string targetPath)
        {
            using (var z = ZipFile.OpenRead(sourceZip))
            {
                foreach (var entry in z.Entries)
                {                    
                    using (var r = new StreamReader(entry.Open()))
                    {
                        string uncompressedFile = Path.Combine(targetPath, entry.Name);
                        File.WriteAllText(uncompressedFile,r.ReadToEnd());
                    }
                }
            }

        }

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