在C#中从多个内存文件创建Zip压缩文件

38

有没有一种方法可以创建一个包含多个文件的Zip存档,而这些文件目前在内存中? 我要保存的文件实际上只是纯文本,并且存储在我的应用程序中的字符串类中。 但我希望将多个文件保存在单个自包含存档中。 它们都可以位于存档的根目录中。

如果能使用SharpZipLib实现这一点就太好了。

8个回答

28

使用 ZipEntryPutNextEntry() 完成此操作。以下是如何对文件执行此操作的示例,但对于内存中的对象,只需使用 MemoryStream

FileStream fZip = File.Create(compressedOutputFile);
ZipOutputStream zipOStream = new ZipOutputStream(fZip);
foreach (FileInfo fi in allfiles)
{
    ZipEntry entry = new ZipEntry((fi.Name));
    zipOStream.PutNextEntry(entry);
    FileStream fs = File.OpenRead(fi.FullName);
    try
    {
        byte[] transferBuffer[1024];
        do
        {
            bytesRead = fs.Read(transferBuffer, 0, transferBuffer.Length);
            zipOStream.Write(transferBuffer, 0, bytesRead);
        }
        while (bytesRead > 0);
    }
    finally
    {
        fs.Close();
    }
}
zipOStream.Finish();
zipOStream.Close();

你可以在 zipOStream 上使用 using() 代替 Close() 吗? - Jay Bazuzi
是的,ZipOutputStream是可处理的。 - Mike
3
如果您正在使用.NET 4.5,则内置的Zip类比这个可怕的SharpZipLib要容易得多。 - The Muffin Man
1
我认为这需要在每次枚举之后(即在foreach结束时)使用zipOStream.CloseEntry()。-- https://github.com/icsharpcode/SharpZipLib/wiki/Zip-Samples#-create-a-zip-with-full-control-over-contents - drzaus
2
@TheMuffinMan,能否提供一下替代方案的链接或详细说明?这个回答是否已经涵盖了它?这个答案是否已经解决了问题? - drzaus

21

使用SharpZipLib似乎相当复杂。 在DotNetZip中这要简单得多。在v1.9中,代码如下:

using (ZipFile zip = new ZipFile())
{
    zip.AddEntry("Readme.txt", stringContent1);
    zip.AddEntry("readings/Data.csv", stringContent2);
    zip.AddEntry("readings/Index.xml", stringContent3);
    zip.Save("Archive1.zip"); 
}

以上代码假定stringContent{1,2,3}包含要存储在zip归档文件(或条目)中的数据。第一个条目是"Readme.txt",它存储在zip归档文件的顶级"Directory"中。接下来的两个条目存储在zip归档文件的"readings"目录中。

这些字符串使用默认编码进行编码。此处未显示AddEntry()的重载版本,它允许您显式指定要使用的编码。

如果您将内容存储在流或字节数组中而不是字符串中,则有AddEntry()的重载版本接受这些类型。还有一些重载版本可以接受Write委托,即您的方法用于将数据写入zip。例如,这适用于轻松将DataSet保存到zip文件中。

DotNetZip是免费且开源的。


10

这个函数应该从数据流中创建一个字节数组:我为了简单起见创建了一个处理文件的简单接口。

public interface IHasDocumentProperties
{
    byte[] Content { get; set; }
    string Name { get; set; }
}

public void CreateZipFileContent(string filePath, IEnumerable<IHasDocumentProperties> fileInfos)
{    
    using (var memoryStream = new MemoryStream())
    {
        using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
        {
            foreach(var fileInfo in fileInfos)
            {
                var entry = zipArchive.CreateEntry(fileInfo.Name);

                using (var entryStream = entry.Open())
                {
                    entryStream.Write(fileInfo.Content, 0, fileInfo.Content.Length);
                }                        
            }
        }
       
        using (var fileStream = new FileStream(filePath, FileMode.OpenOrCreate, System.IO.FileAccess.Write))
        {
            memoryStream.Position = 0;
            memoryStream.CopyTo(fileStream);
        }
    }
}

这很好,除了您在处理完MemoryStream后继续使用它。 - John Gilmer
1
好的,我已经修复了。 - johnny 5
谢谢,但是使用您的代码生成的zip文件大小为0字节,问题在于将memoryStream复制到fileStream时,需要在CopyTo之前将memoryStream的位置设置为0,即memoryStream.Position = 0; 然后它就可以按预期工作了。参考:https://dev59.com/8WMl5IYBdhLWcg3wAC6h#18766138 - miguelug
2
@miguelug 谢谢,我已经更新了我的答案。 - johnny 5

5

我遇到了一个问题,使用MSDN提供的例子,我创建了这个类:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.IO.Packaging;  
using System.IO;  

public class ZipSticle  
{  
    Package package;  

    public ZipSticle(Stream s)  
    {  
        package = ZipPackage.Open(s, FileMode.Create);  
    }  

    public void Add(Stream stream, string Name)  
    {  
        Uri partUriDocument = PackUriHelper.CreatePartUri(new Uri(Name, UriKind.Relative));  
        PackagePart packagePartDocument = package.CreatePart(partUriDocument, "");  

        CopyStream(stream, packagePartDocument.GetStream());  
        stream.Close();  
    }  

    private static void CopyStream(Stream source, Stream target)  
    {  
        const int bufSize = 0x1000;  
        byte[] buf = new byte[bufSize];  
        int bytesRead = 0;  
        while ((bytesRead = source.Read(buf, 0, bufSize)) > 0)  
            target.Write(buf, 0, bytesRead);  
    }  

    public void Close()  
    {  
        package.Close();  
    }  
}

您可以像这样使用它:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);  
ZipSticle zip = new ZipSticle(str);  

zip.Add(File.OpenRead("C:/Users/C0BRA/SimpleFile.txt"), "Some directory/SimpleFile.txt");  
zip.Add(File.OpenRead("C:/Users/C0BRA/Hurp.derp"), "hurp.Derp");  

zip.Close();
str.Close();

您可以将MemoryStream(或任何Stream)传递给ZipSticle.Add,例如:
FileStream str = File.Open("MyAwesomeZip.zip", FileMode.Create);  
ZipSticle zip = new ZipSticle(str);  

byte[] fileinmem = new byte[1000];
// Do stuff to FileInMemory
MemoryStream memstr = new MemoryStream(fileinmem);
zip.Add(memstr, "Some directory/SimpleFile.txt");

memstr.Close();
zip.Close();
str.Close();

5
是的,您可以使用SharpZipLib来完成此操作 - 当您需要提供一个流以写入时,请使用MemoryStream

2
我使用了Cheeso的答案,通过将MemoryStream作为不同Excel文件的源添加。当我下载zip文件时,文件里面没有任何内容。这可能是我们试图通过AJAX创建和下载文件的方式。为了让不同Excel文件的内容包含在Zip中,我必须将每个文件都添加为一个byte[]。
using (var memoryStream = new MemoryStream())
using (var zip = new ZipFile())
{
    zip.AddEntry("Excel File 1.xlsx", excelFileStream1.ToArray());
    zip.AddEntry("Excel File 2.xlsx", excelFileStream2.ToArray());

    // Keep the file off of disk, and in memory.
    zip.Save(memoryStream);
}

2

请注意,此答案已经过时;自 .Net 4.5 开始,ZipArchive 类允许在内存中压缩文件。有关如何使用,请参见下面johnny 5的答案


您也可以以稍微不同的方式完成,使用可序列化对象来存储所有字符串。

[Serializable]
public class MyStrings {
    public string Foo { get; set; }
    public string Bar { get; set; }
}

然后,您可以将其序列化为流以保存它。
为节省空间,您可以使用GZipStream(来自System.IO.Compression)进行压缩。(注意:GZip是流压缩,而不是多个文件的存档)。
当然,如果您需要保存数据而不是在特定格式中压缩几个文件,则可以这样做。
此外,这将允许您保存除字符串以外的许多其他类型的数据。

我原本想把保存文件制作成一种格式,让用户可以打开它并提取特定的部分,但这可能是最好/最容易做的事情... - Adam Haile
@配置器 很高兴在这里见到你 - johnny 5

-1
使用StringReader从字符串对象中读取并将其作为流暴露出来,这样可以轻松地将它们提供给您的zip构建代码。

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