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文件打开以添加更多文件?
我查看了你的代码并立即发现了问题.现在很多软件开发人员面临的问题是,他们现在不了解这些东西是如何工作的,这使得无法对其进行推理.在这种特殊情况下,您似乎不知道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)
在我的电脑上,这需要大约一个小时.
现在,并非所有文件格式细节都相关.我们正在寻找在您的计划中变得越来越糟糕的东西.
略过文件格式规范,您会注意到压缩基于Deflate,它不需要有关压缩的其他文件的信息.继续,我们将注意到'文件表'如何存储在ZIP文件中:

你会注意到这里有一个'中心目录',它将文件存储在ZIP文件中.它基本上存储为"列表".因此,使用此信息,我们可以推断在按此顺序实施步骤(1-3)时更新的方法是什么:
想一想,对于文件#1,你需要1次写操作; 对于文件#2,你需要读取(1项),追加(在内存中)和写入(2项); 对于文件#3,您需要读取(2项),追加(在内存中)和写入(3项).等等.这基本上意味着如果添加更多文件,您的性能将会下降.你已经观察到了这一点,现在你知道了为什么.
可能的解决方案
在之前的解决方案中,我一次添加了所有文件.这可能不适用于您的用例.另一个解决方案是实现一个基本上每次合并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秒内完成.好多了.
您正在重复打开文件,为什么不添加循环并将它们全部添加到一个 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,添加一个,然后关闭它。等又进来几个,分批添加。