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.ZlibCodec.Inflate(FlushType flash)at Ionic.Zlib.InflateManager.Inflate(FlushType flush)at Ionic.Zlib.ZlibBaseStream.Read(Byte []バッファ、Int32オフセット、Int32カウント)at Ionic.Zlib.DeflateStream.Read(Byte []バッファ、Int32オフセット、Int32カウント)at Ionic.Crc.CrcCalculatorStream.Read(Byte []バッファ、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.ZipEntry.Write(Stream s)at Ionic .Zip.ZipFile.Save()at Ionic.Zip.ZipFile.Save(Stream outputStream)at

私は何が間違っているのですか?

4

3 に答える 3

8

間違っていることは次のとおりです。単一のZipFileインスタンスでZipEntry.OpenReader()への保留中の呼び出しが複数あります。保留中のZipEntry.OpenReader()は最大で1つだけです。

その理由は次のとおりです。ZipFile.Read()または新しいZipFile()を使用して特定のzipファイルをインスタンス化し、既存のファイルの名前を渡すと、作成されるStreamオブジェクトは1つだけです。ZipEntry.OpenReader()を呼び出すと、StreamオブジェクトにSeek()が生成され、ファイルポインターがその特定のエントリの圧縮されたバイトストリームの先頭に移動します。ZipEntry.OpenReader()を再度呼び出すと、ストリーム内の別の場所に別のSeek()が表示されます。したがって、エントリを追加してOpenReader()を連続して呼び出すと、Seek()が繰り返し呼び出されますが、有効になるのは最後の1つだけです。ストリームカーソルは、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ファイルごとに、2つのZipFileインスタンスを作成する必要があります。元のzipアーカイブを読んで最初のものを開きます。保持するエントリごとに、MemoryStreamを作成し、エントリからそのMemoryStreamにコンテンツを抽出し、memストリームでSeek()を呼び出して、メモリストリームのカーソルをリセットすることを忘れないでください。次に、2番目のZipFileインスタンスを使用して、AddEntry()を呼び出し、そのMemoryStreamを追加されたエントリのソースとして使用します。2番目のインスタンスでのみ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()オーバーロードを呼び出します。これは、エントリが新しい小さいzipファイルに書き込まれるまでOpenReader()の呼び出しを延期します。その結果、一度に保留中のOpenReader()は1つだけになります。

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ライブラリが実際に問題の原因であると仮定します)

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 に答える