将文件添加到现有Zip中 - 性能问题

Ana*_*nas 17 c# performance wcf dotnetzip

我有一个WCF Web服务,可以将文件保存到一个文件夹(大约200,000个小文件).之后,我需要将它们移动到另一台服务器.

我发现的解决方案是拉链然后移动它们.

当我采用这个解决方案时,我已经用(20,000个文件)进行了测试,压缩20,000个文件只需要大约2分钟,并且移动zip非常快.但在生产中,压缩200,000个文件需要2个多小时.

这是我压缩文件夹的代码:

using (ZipFile zipFile = new ZipFile())
{
    zipFile.UseZip64WhenSaving = Zip64Option.Always;
    zipFile.CompressionLevel = CompressionLevel.None;
    zipFile.AddDirectory(this.SourceDirectory.FullName, string.Empty);

    zipFile.Save(DestinationCurrentFileInfo.FullName);
}
Run Code Online (Sandbox Code Playgroud)

我想修改WCF Web服务,以便保存到zip文件而不是保存到文件夹.

我使用以下代码进行测试:

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));

foreach (var additionFile in listAes)
{
    using (var zip = ZipFile.Read(nameOfExistingZip))
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        zip.AddFile(additionFile.FullName);

        zip.Save();
    }

    file.WriteLine("Delay for adding a file  : " + sw.Elapsed.TotalMilliseconds);
    sw.Restart();
}
Run Code Online (Sandbox Code Playgroud)

添加到zip的第一个文件只需5毫秒,但要添加的第10,000个文件需要800毫秒.

有没有办法优化这个?或者,如果您有其他建议?

编辑

上面显示的示例仅用于测试,在WCF Web服务中,我将有不同的请求发送我需要添加到Zip文件的文件.由于WCF是无规则的,每次调用我都会有一个新类的实例,那么如何保持Zip文件打开以添加更多文件?

atl*_*ste 8

我查看了你的代码并立即发现了问题.现在很多软件开发人员面临的问题是,他们现在不了解这些东西是如何工作的,这使得无法对其进行推理.在这种特殊情况下,您似乎不知道ZIP文件的工作方式; 因此,我建议你先阅读他们的工作方式,并试图分解引擎盖下发生的事情.

推理

现在我们都在关于它们如何工作的相同页面上,让我们通过使用源代码分解它的工作原理来开始推理; 我们将继续前进:

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));

foreach (var additionFile in listAes)
{
    // (1)
    using (var zip = ZipFile.Read(nameOfExistingZip))
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        // (2)
        zip.AddFile(additionFile.FullName);

        // (3)
        zip.Save();
    }

    file.WriteLine("Delay for adding a file  : " + sw.Elapsed.TotalMilliseconds);
    sw.Restart();
}
Run Code Online (Sandbox Code Playgroud)
  • (1)打开一个ZIP文件.您正在为尝试添加的每个文件执行此操作
  • (2)将单个文件添加到ZIP文件
  • (3)保存完整的ZIP文件

在我的电脑上,这需要大约一个小时.

现在,并非所有文件格式细节都相关.我们正在寻找在您的计划中变得越来越糟糕的东西.

略过文件格式规范,您会注意到压缩基于Deflate,它不需要有关压缩的其他文件的信息.继续,我们将注意到'文件表'如何存储在ZIP文件中:

Zip文件结构

你会注意到这里有一个'中心目录',它将文件存储在ZIP文件中.它基本上存储为"列表".因此,使用此信息,我们可以推断在按此顺序实施步骤(1-3)时更新的方法是什么:

  • 打开zip文件,阅读中心目录
  • 附加(新)压缩文件的数据,将指针与文件名一起存储在新的中央目录中.
  • 重写中央目录.

想一想,对于文件#1,你需要1次写操作; 对于文件#2,你需要读取(1项),追加(在内存中)和写入(2项); 对于文件#3,您需要读取(2项),追加(在内存中)和写入(3项).等等.这基本上意味着如果添加更多文件,您的性能将会下降.你已经观察到了这一点,现在你知道了为什么.

可能的解决方案

在之前的解决方案中,我一次添加了所有文件.这可能不适用于您的用例.另一个解决方案是实现一个基本上每次合并2个文件的合并.如果在启动压缩过程时没有所有文件可用,则更方便.

基本上算法变成:

  1. 添加一些(比如16个文件).你可以玩这个号码.将其存储在-say-'file16.zip'中.
  2. 添加更多文件.当您点击16个文件时,您必须将16个项目的两个文件合并为一个包含32个项目的文件.
  3. 合并文件,直到无法合并为止.基本上每次有两个N项的文件时,就会创建一个2*N项的新文件.
  4. 转到(2).

我们可以再次推理它.前16个文件不是问题,我们已经确定了.

我们也可以推断我们的计划会发生什么.因为我们将2个文件合并到1个文件中,所以我们不必进行尽可能多的读写操作.事实上,如果你推理它,你会看到你有2个合并中的32个条目的文件,4个合并中的64个,8个合并中的128个,16个合并中的256个...嘿,等我们知道这个序列,它是2^N.再次,推理它我们会发现我们需要大约500次合并 - 这比我们开始的200,000次操作要好得多.

黑客入侵ZIP文件

可能会想到的另一个解决方案是过度分配中央目录,为将来添加的条目创建松弛空间.但是,这可能需要您入侵邮政编码并创建自己的ZIP文件编写器.我们的想法是,在开始之前,您基本上将中心目录分配到200K条目,这样您就可以简单地追加到位.

同样,我们可以推断它:现在添加文件意味着:添加文件并更新一些标题.它不会像原始解决方案那么快,因为你需要随机磁盘IO,但它可能足够快.

我没有解决这个问题,但对我来说这似乎并不太复杂.

最简单的解决方案是最实用的

到目前为止我们还没有讨论过最简单的解决方案:我想到的一种方法是简单地一次添加所有文件,我们可以再次推理.

实施很容易,因为现在我们不需要做任何花哨的事情; 我们可以简单地使用ZIP处理程序(我使用离子):

static void Main()
{
    try { File.Delete(@"c:\tmp\test.zip"); }
    catch { }

    var sw = Stopwatch.StartNew();

    using (var zip = new ZipFile(@"c:\tmp\test.zip"))
    {
        zip.UseZip64WhenSaving = Zip64Option.Always;
        for (int i = 0; i < 200000; ++i)
        {
            string filename = "foo" + i.ToString() + ".txt";
            byte[] contents = Encoding.UTF8.GetBytes("Hello world!");
            zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
            zip.AddEntry(filename, contents);
        }

        zip.Save();
    }

    Console.WriteLine("Elapsed: {0:0.00}s", sw.Elapsed.TotalSeconds);
    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)

Whop; 在4,5秒内完成.好多了.


DLe*_*Leh 0

您正在重复打开文件,为什么不添加循环并将它们全部添加到一个 zip 中,然后保存它?

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories)
    .Where(s => s.EndsWith(".aes"))
    .Select(f => new FileInfo(f));

using (var zip = ZipFile.Read(nameOfExistingZip))
{
    foreach (var additionFile in listAes)
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        zip.AddFile(additionFile.FullName);
    }
    zip.Save();
}
Run Code Online (Sandbox Code Playgroud)

如果文件不能立即全部可用,您至少可以将它们批处理在一起。因此,如果您预计有 20 万个文件,但到目前为止您只收到了 10 个,请不要打开 zip,添加一个,然后关闭它。等又进来几个,分批添加。