System.IO.Compression.Zip存档内存管理

Ste*_*old 3 c# compression zip .net-4.5

在.Net 4.5中,System.IO.Compression.ZipArchive类获得了一些更新。

如此处可读(http://msdn.microsoft.com/en-us/magazine/jj133817.aspx)所示,它现在应该执行“典型操作不需要将整个档案读入内存”。

为了进行测试,我尝试压缩10个文件,每个文件200MB。

如果您使用以下代码创建新的zip存档,则效果很好(在整个过程中内存使用率较低):

for (int directoryGroupIndex = 0; directoryGroupIndex < directoryGroups.Count; directoryGroupIndex++)
{
  String directoryGroupKey = directoryGroups.Keys.ElementAt(directoryGroupIndex);
  FileInfo[] directoryGroup = directoryGroups[directoryGroupKey];

  String archiveFileName = String.Format("Readed Logfiles{0}", archiveFileExtension);
  String archiveFileFullName = Path.Combine(directoryGroupKey, archiveFileName);
  FileInfo archiveFile = new FileInfo(archiveFileFullName);


  using (FileStream archiveFileStream = new FileStream(archiveFile.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
  using (ZipArchive archive = new ZipArchive(archiveFileStream, ZipArchiveMode.Create, false))
  {
    for (int directoryGroupFileIndex = 0; directoryGroupFileIndex < directoryGroup.Length; directoryGroupFileIndex++)
    {
      FileInfo file = directoryGroup[directoryGroupFileIndex];
      String archiveEntryName = file.Name;
      String archiveEntryPath = DateTime.Now.ToString("yyyy-MM-dd");
      String archiveEntryFullName = Path.Combine(archiveEntryPath, archiveEntryName);

      ZipArchiveEntry archiveEntry = archive.CreateEntryFromFile(file.FullName, archiveEntryFullName, CompressionLevel.Optimal);
    }
  }              
}
Run Code Online (Sandbox Code Playgroud)

现在,我想向该存档添加新条目。我将代码保持原样,然后再次运行。(在根目录中有新文件)如果我查看文档,则会读到“只允许创建新的存档条目”。所以我的代码应该没问题。

现在的结果是:

  1. 存档内的文件表将被覆盖(仅列出新文件)。

  2. 存档文件的大小已增加(就像旧文件一样)。

  3. 档案已损坏。您可以打开它,但不能取消内容的目录设置。

如果我将ZipArchiveMode更改为“ ZipArchiveMode.Update”,则它将按预期工作,但仅适用于小文件。像my这样的文件会抛出内存不足异常,因为完整的存档已加载到内存中。

我现在的问题是:我做错了吗,这是错误还是设计缺陷?

Pet*_*iho 5

您编写的代码使ZipArchive该类在上一个文件的末尾写入一个全新的存档,这当然会损坏文件。

执行所需操作的方法是在创建原始归档文件时将其复制到新文件,然后用新文件替换原始文件。例如:

string tempFile = Path.GetTempFileName();

using (ZipArchive original =
    new ZipArchive(File.Open(archiveFileStream, FileMode.Open), ZipArchiveMode.Read))
using (ZipArchive newArchive =
    new ZipArchive(File.Open(tempFile, FileMode.Create), ZipArchiveMode.Create))
{
    foreach (ZipArchiveEntry entry in original.Entries)
    {
        ZipArchiveEntry newEntry = newArchive.Create(entry.FullName);

        using (Stream source = entry.Open())
        using (Stream destination = newEntry.Open())
        {
            source.CopyTo(destination);
        }
    }

    for (int directoryGroupFileIndex = 0;
            directoryGroupFileIndex < directoryGroup.Length;
            directoryGroupFileIndex++)
    {
        FileInfo file = directoryGroup[directoryGroupFileIndex];
        String archiveEntryName = file.Name;
        String archiveEntryPath = DateTime.Now.ToString("yyyy-MM-dd");
        String archiveEntryFullName = Path.Combine(archiveEntryPath, archiveEntryName);

        ZipArchiveEntry archiveEntry = newArchive.CreateEntryFromFile(
            file.FullName, archiveEntryFullName, CompressionLevel.Optimal);
    }
}

File.Delete(archiveFileStream);
File.Move(tempFile, archiveFileStream);
Run Code Online (Sandbox Code Playgroud)

请注意,这实际上不会比慢ZipArchiveMode.Update。当您使用更新模式时,ZipArchive该类将整个存档读入内存(如您所述),然后在您关闭它时,它将重新压缩并将所有内容写回。

上面的代码基本上执行完全相同的计算,但是只是将磁盘用作中间存储而不是内存。

  • “文件并未真正被覆盖”-这是因为您没有要求文件被覆盖。请改用`FileMode.Create`,它将被覆盖。简单地附加到.zip存档存在一个基本问题,那就是存在一个校验和,需要考虑文件中数据的全部。至少,必须对原件进行处理才能正确地做到这一点;您不能只是追加到现有文件。而且诸如WinRAR之类的工具可以恢复损坏的文件,并没有说明“ ZipArchive”应该做什么。这只是意味着WinRAR是有用的。 (2认同)