DotNetZip从其他zip的子集创建zip

时间:2011-10-31 20:13:50

标签: c# .net dotnetzip

我有一个大的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)at   Ionic.Zlib.ZlibBaseStream.Read(Byte []缓冲区,Int32偏移量,Int32   在Ionic.Zlib.DeflateStream.Read(Byte []缓冲区,Int32偏移量,   在Ionic.Crc.CrcCalculatorStream.Read(Byte []缓冲区的Int32计数),   Int32偏移量,Int32计数)at   Ionic.Zip.SharedUtilities.ReadWithRetry(Stream s,Byte [] buffer,Int32   offset,Int32 count,String FileName)at   Ionic.Zip.ZipEntry._WriteEntryData(Stream s)at   Ionic.Zip.ZipFile.Save()处的Ionic.Zip.ZipEntry.Write(Stream s)   

的Ionic.Zip.ZipFile.Save(Stream outputStream)

我做错了什么?

3 个答案:

答案 0 :(得分:8)

这就是你做错了:你在一个ZipFile实例中有多个对ZipEntry.OpenReader()的挂起调用。最多只能有一个挂起的ZipEntry.OpenReader()。

原因如下:当您使用ZipFile.Read()或new ZipFile()实例化给定的zip文件时,只创建了一个Stream对象,并传递现有文件的名称。当您调用ZipEntry.OpenReader()时,它会在Stream对象中生成Seek(),以将文件指针移动到该特定条目的压缩字节流的开头。当您再次调用ZipEntry.OpenReader()时,它会导致另一个Seek()到流中的其他位置。因此,通过添加条目并连续调用OpenReader(),您将重复调用Seek(),但只有最后一个有效。流游标将放置在与最后一次调用ZipEntry.OpenReader()相对应的条目的数据的开头。

修复它:废弃你的方法。使用比现有zip文件更少的条目创建新zipfile的最简单方法是:通过读取现有文件来实例化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,这是低效且不必要的。


如果您不介意解压缩和重新压缩的效率低下,可以采用另一种方法:调用the ZipFile.AddEntry() overload that accepts opener and closer delegates。这样做是将对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");
}

它仍然效率低下,因为每个条目都经过解压缩和重新压缩,但效率低一点。

答案 1 :(得分: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");

答案 2 :(得分:0)

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

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

我在代码中一直使用相同的库来生成.zips,但我从来没有像你想要的那样完成它。我不知道为什么你的代码会给你一个例外,但也许这会有效吗? (假设你的字符串/路径名都是正确的,而zip-library确实是导致问题的原因)

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");
}