C# System.IO.Compression.ZipArchive内存管理

C# System.IO.Compression.ZipArchive内存管理,c#,zip,compression,.net-4.5,C#,Zip,Compression,.net 4.5,在.Net 4.5中,System.IO.Compression.ZipArchive类得到一些更新 正如这里的可读性()一样,它现在应该执行“典型操作不需要将整个归档文件读入内存” 为了进行测试,我尝试压缩10个文件,每个文件大小为200MB 如果您使用此代码创建新的zip归档文件(整个过程内存使用率较低),这将非常有用: (int directoryGroupIndex=0;directoryGroupIndex

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

正如这里的可读性()一样,它现在应该执行“典型操作不需要将整个归档文件读入内存”

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

如果您使用此代码创建新的zip归档文件(整个过程内存使用率较低),这将非常有用:

(int directoryGroupIndex=0;directoryGroupIndex { 字符串directoryGroupKey=directoryGroups.Keys.ElementAt(directoryGroupIndex); FileInfo[]directoryGroup=directoryGroups[directoryGroupKey]; String archiveFileName=String.Format(“读取的日志文件{0}”,archiveFileExtension); 字符串archiveFileFullName=Path.Combine(directoryGroupKey,archiveFileName); FileInfo archiveFile=新文件信息(archiveFileFullName); 使用(FileStream archiveFileStream=newfilestream(archiveFile.FullName、FileMode.OpenOrCreate、FileAccess.Write、FileShare.Read)) 使用(ZipArchive archive=new ZipArchive(archiveFileStream,ZipArchiveMode.Create,false)) { 对于(int directoryGroupFileIndex=0;directoryGroupFileIndex 现在我想将新的条目添加到此存档。我让代码保持原样,然后再次运行它。(根目录中有新文件)如果我查看文档,我会读到“只允许创建新的归档条目”,这正是我想要的。所以我的代码应该很好

结果是:

  • 存档中的文件表将被覆盖(仅列出新文件)

  • 存档文件的大小已经增加(就像旧文件仍然在那里一样)

  • 档案已损坏。您可以打开它,但无法解压缩内容

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


    我现在的问题是:我做错了吗?这是一个bug还是一个设计缺陷?

    您编写的代码导致
    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);
    
    string tempFile=Path.GetTempFileName();
    使用(ZipArchive)原稿=
    新的ZipArchive(File.Open(archiveFileStream,FileMode.Open),ZipArchiveMode.Read))
    使用(ZipArchive)新存档=
    新建ZipArchive(File.Open(tempFile,FileMode.Create),ZipArchiveMode.Create))
    {
    foreach(原始项目中的ZipArchiveEntry项目)
    {
    ZipArchiveEntry newEntry=newArchive.Create(entry.FullName);
    使用(Stream source=entry.Open())
    使用(流目的地=newEntry.Open())
    {
    source.CopyTo(目的地);
    }
    }
    对于(int directoryGroupFileIndex=0;
    directoryGroupFileIndex
    请注意,这实际上不会比ZipArchiveMode.Update慢。当您使用更新模式时,
    ZipArchive
    类将整个存档读取到内存中(如您所述),然后当您关闭它时,它将重新压缩并将所有内容写回内存


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

    您好,谢谢Peter。这听起来是个好办法。但似乎也存在设计缺陷。因为应该可以在不将整个文件加载到内存的情况下向存档中添加一些新条目。正如我写的,如果你尝试它,文件不会被真正覆盖。因为这个尺寸只是在变大。第二,文件被破坏。第三,如果重新修复归档文件(例如使用winrar),它将重新生成所有文件(旧文件和新文件)。因此,我认为ZipArchive扩展文件的方式是正确的,但是没有更新文件表以删除现有文件。“文件没有真正被覆盖”——这是因为您没有要求覆盖文件。使用
    FileMode。改为创建
    ,它将被覆盖。简单地附加到.zip归档文件有一个基本问题,即需要考虑文件中所有数据的校验和。至少,必须对原件进行处理才能正确地做到这一点;您不能只是附加到现有文件。像WinRAR这样的工具可以恢复损坏的文件,这并不能说明ZipArchive应该做什么;这只是意味着WinRAR很有用。
    
    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);