DotNetZip从其他zip文件的子集创建zip

3
我有一个很大的zip文件,需要将其拆分成多个zip文件。我现在正在创建的方法中使用了List对象。
这是我得到的代码:
 //All files have the same basefilename/
 string basefilename = Path.GetFileNameWithoutExtension(entries[0].FileName);
 MemoryStream memstream = new MemoryStream();
 ZipFile zip = new ZipFile();
 foreach (var entry in entries)
 {
    string newFileName = basefilename + Path.GetExtension(entry.FileName);
    zip.AddEntry(newFileName, entry.OpenReader());
 }

 zip.Save(memstream);

 //this will later go in an file-io handler class.
 FileStream outstream = File.OpenWrite(@"c:\files\"+basefilename+ ".zip");
 memstream.WriteTo(outstream);
 outstream.Flush();
 outstream.Close();

这是我在调用save()方法时遇到的错误:

{Ionic.Zlib.ZlibException: 状态异常(无效块类型) 在Ionic.Zlib.InflateManager.Inflate(FlushType flush) at Ionic.Zlib.ZlibCodec.Inflate(FlushType flush) 在 Ionic.Zlib.ZlibBaseStream.Read(Byte[] buffer, Int32 offset, Int32 count) 在Ionic.Zlib.DeflateStream.Read(Byte[] buffer, Int32 offset, Int32 count) 在Ionic.Crc.CrcCalculatorStream.Read(Byte[] buffer, Int32 offset, Int32 count) 在 Ionic.Zip.SharedUtilities.ReadWithRetry(Stream s, Byte[] buffer, Int32 offset, Int32 count, String FileName) 在 Ionic.Zip.ZipEntry._WriteEntryData(Stream s) 在 Ionic.Zip.ZipEntry.Write(Stream s) 在Ionic.Zip.ZipFile.Save() 在 Ionic.Zip.ZipFile.Save(Stream outputStream) 在

我做错了什么?


哪一行引起了错误? - sq33G
3个回答

8
这是你的错误:在单个ZipFile实例中有多个待处理的ZipEntry.OpenReader()调用。最多只能有一个待处理的ZipEntry.OpenReader()。
原因是:当使用ZipFile.Read()或new ZipFile()并传递现有文件的名称来实例化给定的zip文件时,只创建一个Stream对象。当调用ZipEntry.OpenReader()时,会导致在Stream对象中进行Seek(),将文件指针移动到该特定条目的压缩字节流的开头。再次调用ZipEntry.OpenReader()时,会导致在流中不同位置的另一个Seek()。因此,通过添加条目并连续调用OpenReader(),会重复调用Seek(),但只有最后一个是有效的。流光标将被放置在与最后一个对ZipEntry.OpenReader()的调用相对应的条目数据的开头。
解决方法:放弃你的方法。创建少于现有zip文件的条目数的新zip文件的最简单方法是:通过读取现有文件来实例化ZipFile,然后删除您不想要的条目,然后调用ZipFile.Save()保存到新路径。
using (var zip = ZipFile.Read("c:\\dir\\path\\to\\existing\\zipfile.zip")) 
{
    foreach (var name in namesToRemove) // IEnumerable<String>
    {
       zip[name].Remove();
    }
    zip.Save("c:\\path\\to\\new\\Archive.zip");
} 
编辑
在调用Save()时,这个库会读取你没有从文件系统中删除的条目的原始压缩数据,并将它们写入一个新的归档文件中。这非常快,因为它不需要对每个条目进行解压缩和重新压缩,以便将其放入新的、更小的zip文件中。基本上,它从原始zip文件中读取二进制数据片段,并将它们连接在一起形成新的、更小的zip文件。要生成多个较小的文件,可以反复使用原始zip文件进行操作;只需将以上内容包装在循环中,然后更改您要删除的文件和新的较小存档文件的文件名即可。读取现有zip文件也非常快速。
作为另一种选择,您可以解压并提取每个条目,然后重新压缩并将该条目写入新的zip文件。这是一种冗长的方法,但是可行的。在这种情况下,对于要创建的每个较小的zip文件,您需要创建两个ZipFile实例。通过读取原始zip存档来打开第一个。对于每个要保留的条目,请创建一个MemoryStream,从一个条目中提取内容到该MemoryStream中,并记得在mem流上调用Seek()以重置内存流上的光标。然后使用第二个ZipFile实例,调用AddEntry(),使用该MemoryStream作为添加条目的源。仅在第二个实例上调用ZipFile.Save()。
using (var orig = ZipFile.Read("C:\\whatever\\OriginalArchive.zip"))
{
    using (var smaller = new ZipFile())
    {
      foreach (var name in entriesToKeep) 
      { 
         var ms = new MemoryStream();
         orig[name].Extract(ms); // extract into stream
         ms.Seek(0,SeekOrigin.Begin);
         smaller.AddEntry(name,ms);
      }
      smaller.Save("C:\\location\\of\\SmallerZip.zip");
    }   
}

这样做虽然可行,但涉及将进入较小zip的每个条目进行解压缩和重新压缩,这是低效且不必要的。
如果您不介意解压缩和重新压缩的低效率,可以采用另一种方法:调用接受opener和closer委托的ZipFile.AddEntry()重载。这样做的效果是将OpenReader()的调用推迟到写入新的更小的zip文件时。这样,您每次只有一个待处理的OpenReader()。
using(ZipFile original = ZipFile.Read("C:\\path.to\\original\\Archive.zip"),
      smaller = new ZipFile())
{
    foreach (var name in entriesToKeep)
    {
        zip.AddEntry(zipEntryName,
                     (name) => original[name].OpenReader(),
                     null);
    }

    smaller.Save("C:\\path.to\\smaller\\Archive.zip");
}

这仍然是低效的,因为每个条目都会被解压缩和重新压缩,但它稍微高效了一点。


可能问题确实出在OpenReader()函数上。问题是,我需要将一个zip文件分成大约20个较小的zip文件。客户通过网站将一个包含60个文件的zip文件放置在某个位置。当我们处理这个zip文件时,其中包含.cfg、.txt和.htm文件。每个CFG文件都会生成一个数据库记录。每个数据库记录都需要附带一个包含cfg、txt和htm文件的zip文件。 - Patrick
听起来你需要强制新的zip立即读取每个旧条目,而不是构建一个列表并告诉它一次性读/写所有条目。在循环中包含save()能实现这个吗? - sq33G
@Patrick,我不知道为什么你的“东西”让你觉得建议的解决方案行不通。首先,从一个 zip 文件开始,然后对于每种类型的想要的小 zip 文件,按照我上面展示的代码进行操作:打开原始 zip 文件,删除在较小的 zip 中不想要的条目,然后保存到“SmallerZipNNNN.zip”,其中 NNNN 是较小的 zip 的编号。 - Cheeso

1
Cheeso 指出我不能同时打开多个读取器。虽然他的删除解决方案并不是我所需要的。因此,我尝试使用新知识来解决问题,并创建了这个。
string basefilename = Path.GetFileNameWithoutExtension(entries[0].FileName);
ZipFile zip = new ZipFile();
foreach (var entry in entries){
      CrcCalculatorStream reader = entry.OpenReader();
      MemoryStream memstream = new MemoryStream();
      reader.CopyTo(memstream);
      byte[] bytes = memstream.ToArray();
      string newFileName = basefilename + Path.GetExtension(entry.FileName);
      zip.AddEntry(newFileName, bytes);
}

zip.Save(@"c:\files\" + basefilename + ".zip");

Patrick - 很高兴你找到了一个可行的解决方案。你在这里所做的是提取和解压条目,然后将这些条目压缩成一个新的 zip 文件。正如你所说,它可以工作。我提出的方法也可以工作,只是它不需要解压和重新压缩任何内容。它只是写入一个新的 zip 文件,跳过你“不想要”的条目。 - Cheeso

0

编辑2:在指定路径名时,我认为您需要双反斜杠。我更新了我的代码以反映这一点。双反斜杠用于在字符串中表示一个普通的反斜杠。

编辑:变量“newFileName”是否代表文件当前所在的路径?如果这个变量是其他东西,那可能就是你的问题。没有看到更多周围的代码,我不确定。

我经常在我的代码中使用相同的库来制作.zip文件,但我从未像你尝试过这样做。我不知道为什么你的代码会给你一个异常,但也许这个方法会起作用?(假设您的字符串/路径名都正确,并且zip库确实是导致问题的原因)

using (ZipFile zip = new ZipFile())
{
   zip.CompressionLevel = CompressionLevel.BestCompression;
   foreach (var entry in entries)
   {
      try
      {
         string newFileName = basefilename + Path.GetExtension(entry.FileName);
         zip.AddFile(newFileName, "");
      }
      catch (Exception) { }
   }
   zip.Save("c:\\files\\"+basefilename+ ".zip");
}

在字符串前面加上@符号可以省略双斜杠。我认为这就是Cheeso所描述的问题。我不能在其中有多个OpenReaders。 - Patrick

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