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) 在 Ionic.Zlib.ZlibCodec.Inflate(FlushType flush) 在 Ionic.Zlib.ZlibBaseStream.Read(Byte [] Ionic.Zlib.DeflateStream.Read(Byte[] buffer, Int32 offset, Int32 count) at Ionic.Crc.CrcCalculatorStream.Read(Byte[] buffer, Int32 offset, Int32 count) 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.ZipEntry.Write(Stream s) at Ionic .Zip.ZipFile.Save() 在 Ionic.Zip.ZipFile.Save(Stream outputStream) 在

我究竟做错了什么?

4

3 回答 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 文件更少的条目创建新 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 文件重复执行此操作;只需将上述内容包装在一个循环中并更改您删除的文件以及新的较小存档的文件名。读取现有的 zipfile 也非常快。


作为替代方案,您可以解压缩并提取每个条目,然后重新压缩并将条目写入新的 zip 文件。这是很长的路要走,但这是可能的。在这种情况下,对于您要创建的每个较小的 zipfile,您将需要创建两个 ZipFile 实例。通过阅读原始 zip 存档打开第一个。对于您要保留的每个条目,创建一个 MemoryStream,从一个条目中提取内容到该 MemoryStream 中,并记住在内存流中调用 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 的每个条目进行解压缩和重新压缩,这是低效且不必要的。


如果您不介意解压缩和重新压缩的低效率,您可以使用另一种方法:调用ZipFile.AddEntry() 重载来接受 opener 和 close 委托。这样做是将调用 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");
}

它仍然是低效的,因为每个条目都会被解压缩和重新压缩,但它的效率要低一些。

于 2011-10-31T21:33:17.273 回答
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");
于 2011-11-01T08:51:55.213 回答
0

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

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

我在我的代码中一直使用相同的库来制作 .zip,但我从来没有像你想要的那样完全做到这一点。我不知道为什么你的代码给你一个例外,但也许这会起作用?(假设您的字符串/路径名都是正确的,并且 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");
}
于 2011-10-31T20:45:13.500 回答