首页 > 其他分享 >.NET 压缩/解压文件

.NET 压缩/解压文件

时间:2024-09-25 13:01:32浏览次数:10  
标签:解压 文件 string zip 压缩 zipFile new NET

.NET 压缩/解压文件

 

本文为大家介绍下.NET解压/压缩zip文件。虽然解压缩不是啥核心技术,但压缩性能以及进度处理还是需要关注下,针对使用较多的zip开源组件验证,给大家提供个技术选型参考

之前在《.NET WebSocket高并发通信阻塞问题 - 唐宋元明清2188 - 博客园 (cnblogs.com)》讲过,团队遇到Zip文件解压进度频率过高问题,也在这里顺带讲下解决方法

目前了解到的常用技术方案有System.IO.Compression、SharpZipLib以及DotNetZip,下面我们分别介绍下使用以及性能

System.IO.Compression

如果你需要处理简单的ZIP压缩和解压任务,且不需要高级特性,建议使用System.IO.Compression。作为.NET标准库的一部分,不需要额外安装第三方库,而且会随着.NET平台的更新而更新

看下代码实现:

复制代码
 1     /// <summary>
 2     /// 解压Zip文件
 3     /// </summary>
 4     /// <param name="filePath">zip文件路径</param>
 5     /// <param name="outputFolder">解压目录</param>
 6     /// <returns></returns>
 7     public static void Decompress(string filePath, string outputFolder)
 8     {
 9         ZipFile.ExtractToDirectory(filePath, outputFolder);
10     }
11 
12     /// <summary>
13     /// 压缩成Zip文件
14     /// </summary>
15     /// <param name="sourceFolder">文件目录</param>
16     /// <param name="zipFile">zip文件路径</param>
17     /// <param name="includeFolder">是否包含文件父目录(即sourceFolder本身)</param>
18     /// <returns></returns>
19     public static void Compress(string sourceFolder, string zipFile, bool includeFolder = true)
20     {
21         ZipFile.CreateFromDirectory(sourceFolder, zipFile, CompressionLevel.Fastest, includeFolder);
22     }
复制代码

优点很明显,API简洁易懂,适用于简单的文件压缩和解压操作。当然提供的功能比较基础,缺乏一些高级特性,比如分卷压缩和加密,也提供不了操作详细进度

我们来测试下解压缩性能,找个zip文件,“智微工厂生产需要的固件及安装包.zip”文件大小847M,里面是如下结构有文件以及文件夹:

解压耗时:8484ms。再将解压后的文件夹压缩,耗时:28672ms。性能整体上还是不错的,特别是解压很优秀
所以呢,比较简单的业务场景可以直接用这个方案。大家可以将这个方案放在公司通用基础技术组件里

SharpZipLib

支持多种压缩格式(如ZIP、TAR、GZIP、BZIP2等),并提供了高级功能如加密、分卷压缩等。icsharpcode/SharpZipLib: #ziplib is a Zip, GZip, Tar and BZip2 library written entirely in C# for the .NET platform. (github.com)

API设计可用性高,满足更多复杂定制化需求。社区里好多小伙伴在使用,开发历史久远、组件稳定性较高

引用下Nuget包SharpZipLib后,解压zip文件

获取压缩包压缩后的文件的大小,这里Size是压缩前大小,还有一个属性CompressedSize压缩后大小:

复制代码
 1         public static long GetZipFileTotalSize(string zipPath)
 2         {
 3             long totalSize = 0;
 4             using FileStream fileStream = File.OpenRead(zipPath);
 5             using ZipInputStream zipStream = new ZipInputStream(fileStream);
 6             while (zipStream.GetNextEntry() is { } zipEntry)
 7             {
 8                 totalSize += zipEntry.Size;
 9             }
10 
11             return totalSize;
12         }
复制代码

解压Zip文件:

复制代码
 1       /// <summary>
 2       /// 解压Zip文件
 3       /// </summary>
 4       /// <param name="zipFile">zip文件路径</param>
 5       /// <param name="outputFolder">解压目录</param>
 6       /// <param name="cancellationToken">取消操作</param>
 7       /// <param name="progressChanged">解压进度回调</param>
 8       /// <returns></returns>
 9       public static async Task UnZipAsync(string zipFile, string outputFolder,
10           CancellationToken cancellationToken = default, Action<ZipProgress> progressChanged = null)
11       {
12           if (!File.Exists(zipFile))
13           {
14               throw new InvalidOperationException($"file not exist,{zipFile}");
15           }
16           var decompressLength = GetZipFileTotalSize(zipFile);
17           using FileStream fileStream = File.OpenRead(zipFile);
18           await Task.Run(() =>
19           {
20               using ZipInputStream zipStream = new ZipInputStream(fileStream);
21               long completedSize = 0;
22               while (zipStream.GetNextEntry() is { } zipEntry)
23               {
24                   if (cancellationToken != default && cancellationToken.IsCancellationRequested)
25                   {
26                       cancellationToken.ThrowIfCancellationRequested();
27                   }
28 
29                   if (zipEntry.IsDirectory)
30                   {
31                       string folder = Path.Combine(outputFolder, zipEntry.Name);
32                       EnsureFolder(folder);
33                   }
34                   else if (zipEntry.IsFile)
35                   {
36                       var operatingSize = completedSize;
37                       var zipEntryName = zipEntry.Name;
38                       string fullEntryPath = Path.Combine(outputFolder, zipEntryName);
39                       string dirPath = Path.GetDirectoryName(fullEntryPath);
40                       EnsureFolder(dirPath);
41                       //解压后的数据
42                       long singleFileSize = WriteUnzipDataToFile(zipStream, fullEntryPath, partialFileSize =>
43                       {
44                           if (progressChanged == null)
45                           {
46                               return;
47                           }
48                           long currentSize = operatingSize + partialFileSize;
49                           progressChanged.Invoke(new ZipProgress(currentSize, decompressLength, zipEntryName));
50                       });
51                       completedSize += singleFileSize;
52                   }
53               }
54           }, cancellationToken);
55       }
复制代码

解压进度能反馈详细的文件写入进度值。另外,这里有个文件夹判断处理,也是支持空文件夹的

Zip压缩,获取所有的文件夹/子文件夹、所有的文件,添加到ZipFile里保存:

复制代码
 1       /// <summary>
 2       /// 压缩文件
 3       /// </summary>
 4       /// <param name="toZipDirectory">待压缩的文件夹</param>
 5       /// <param name="destZipPath">Zip文件的保存路径</param>
 6       /// <returns></returns>
 7       public static bool Zip(string toZipDirectory, string destZipPath)
 8       {
 9           if (string.IsNullOrEmpty(destZipPath))
10           {
11               throw new ArgumentNullException(nameof(destZipPath));
12           }
13           if (!destZipPath.ToUpper().EndsWith(".ZIP"))
14           {
15               throw new ArgumentException("保存路径不是ZIP后缀", nameof(destZipPath));
16           }
17           if (!Directory.Exists(toZipDirectory))
18           {
19               throw new ArgumentException("待压缩的文件夹不存在", nameof(toZipDirectory));
20           }
21 
22           var dirs = Directory.GetDirectories(toZipDirectory, "*", SearchOption.AllDirectories)
23               .Select(dir => PathUtils.GetRelativePath(toZipDirectory, dir));
24           var files = Directory.GetFiles(toZipDirectory, "*", SearchOption.AllDirectories).ToArray();
25           var destFiles = files.Select(file => PathUtils.GetRelativePath(toZipDirectory, file)).ToArray();
26           if (File.Exists(destZipPath))
27           {
28               File.Delete(destZipPath);
29           }
30           using (ZipFile zipFile = ZipFile.Create(destZipPath))
31           {
32               zipFile.BeginUpdate();
33               foreach (var dir in dirs)
34               {
35                   zipFile.AddDirectory(dir);
36               }
37               for (int i = 0; i < files.Length; i++)
38               {
39                   zipFile.Add(files[i], destFiles[i]);
40               }
41               zipFile.CommitUpdate();
42           }
43           return true;
44       }
复制代码

值得一提的是,如有需要指定Zip压缩文件内的文件名以及文件路径,可以在文件时输入对应的压缩后路径定义,注意是指压缩包内的相对路径:

复制代码
 1       /// <summary>指定的文件压缩到对应的压缩文件中</summary>
 2       /// <param name="files">待压缩的文件路径列表(绝对路径)</param>
 3       /// <param name="destFiles">文件路径对应的压缩后路径列表,即压缩后压缩包内的文件路径</param>
 4       /// <param name="destZipPath">Zip文件的保存路径</param>
 5       public static bool Zip(List<string> files, List<string> destFiles, string destZipPath)
 6       {
 7           if (files.Count != destFiles.Count)
 8           {
 9               throw new ArgumentException($"{nameof(files)}与{nameof(destFiles)}文件列表数量不一致");
10           }
11           if (string.IsNullOrEmpty(destZipPath))
12               throw new ArgumentNullException(nameof(destZipPath));
13           using (ZipFile zipFile = ZipFile.Create(destZipPath))
14           {
15               zipFile.BeginUpdate();
16               for (int i = 0; i < files.Count; i++)
17               {
18                   zipFile.Add(files[i], destFiles[i]);
19               }
20               zipFile.CommitUpdate();
21           }
22           return true;
23       }
复制代码

SharpZipLib虽然功能丰富,但大家看上面的demo代码,接口搞的有点复杂、学习曲线较高
同样我们按上面测试操作,解压缩同一zip文件,解压耗时20719ms,压缩耗时102109ms。。。

DotNetZip

再看看DotNetZip,这个相对SharpZipLib,API设计的更友好、容易上手。官网是haf/DotNetZip.Semverd(github.com),它停止维护了。。。作者推荐大家去使用System.IO.Compression!好吧先忽略这个,尽管已不再积极维护,但稳定性、性能真的好,下面给大家列下使用demo和性能测试

 Zip文件解压:

复制代码
 1     /// <summary>
 2     /// 解压Zip文件
 3     /// </summary>
 4     /// <param name="zipFile">zip文件路径</param>
 5     /// <param name="outputFolder">解压目录</param>
 6     /// <param name="password">密码</param>
 7     /// <param name="progressChanged">解压进度回调</param>
 8     /// <returns></returns>
 9     public static void UnZip(string zipFile, string outputFolder, string password, Action<ZipProgress> progressChanged)
10     {
11         if (!File.Exists(zipFile)) throw new InvalidOperationException($"file not exist,{zipFile}");
12         //获取文件解压后的大小
13         var totalZipSize = GetZipFileSize(zipFile);
14         long completedSize = 0L;
15         using (var zip = ZipFile.Read(zipFile))
16         {
17             zip.Password = password;
18             zip.ExtractProgress += (s, e) =>
19             {
20                 if (e.EventType == ZipProgressEventType.Extracting_EntryBytesWritten)
21                 {
22                     var fileName = e.CurrentEntry.FileName;
23                     if (e.BytesTransferred < e.TotalBytesToTransfer)
24                     {
25                         //单个文件解压中的进度
26                         var operatingSize = completedSize + e.BytesTransferred;
27                         progressChanged?.Invoke(new ZipProgress(operatingSize, totalZipSize, fileName));
28                     }
29                     else
30                     {
31                         //单个文件解压完全的进度
32                         completedSize += e.TotalBytesToTransfer;
33                         progressChanged?.Invoke(new ZipProgress(completedSize, totalZipSize, fileName));
34                     }
35                 }
36             };
37             zip.ExtractAll(outputFolder);
38         }
39     }
复制代码

这里获取压缩后文件大小,与上面SharpZipLib的zipEntry.Size对应,取的是zipEntry.UncompressedSize

非常人性的提供了ExtractProgress事件进度,我们取的是Extracting_EntryBytesWritten类型,可以拿到细节进度。具体进度的处理看上方代码

因为反馈的是详细字节写入进度,所以间隔很短。。。1ms都能给你爆几次进度,尤其是大文件:

所以需要限制下回调Action触发,可以加个计时器限制单个文件的进度回调,如100ms内最多触发一次,下面是优化后的代码:

复制代码
 1     /// <summary>
 2     /// 解压Zip文件
 3     /// </summary>
 4     /// <param name="zipFile">zip文件路径</param>
 5     /// <param name="outputFolder">解压目录</param>
 6     /// <param name="password">密码</param>
 7     /// <param name="progressChanged">解压进度回调</param>
 8     /// <returns></returns>
 9     public static void UnZip(string zipFile, string outputFolder, string password,
10         Action<ZipProgress> progressChanged)
11     {
12         if (!File.Exists(zipFile)) throw new InvalidOperationException($"file not exist,{zipFile}");
13         //获取文件解压后的大小
14         var totalZipSize = GetZipFileSize(zipFile);
15         long completedSize = 0L;
16         using (var zip = ZipFile.Read(zipFile))
17         {
18             zip.Password = password;
19             var lastProgressTick = Environment.TickCount;
20             zip.ExtractProgress += (s, e) =>
21             {
22                 if (e.EventType == ZipProgressEventType.Extracting_EntryBytesWritten)
23                 {
24                     var fileName = e.CurrentEntry.FileName;
25                     if (e.BytesTransferred < e.TotalBytesToTransfer)
26                     {
27                         // 单个文件解压变化,限制间隔时间触发解压事件
28                         if (Environment.TickCount - lastProgressTick < ProgressEventTick)
29                         {
30                             return;
31                         }
32                         lastProgressTick = Environment.TickCount;
33                         //单个文件解压中的进度
34                         var operatingSize = completedSize + e.BytesTransferred;
35                         progressChanged?.Invoke(new ZipProgress(operatingSize, totalZipSize, fileName));
36                     }
37                     else
38                     {
39                         //重置计时器
40                         lastProgressTick = Environment.TickCount;
41                         //单个文件解压完全的进度
42                         completedSize += e.TotalBytesToTransfer;
43                         progressChanged?.Invoke(new ZipProgress(completedSize, totalZipSize, fileName));
44                     }
45                 }
46             };
47             zip.ExtractAll(outputFolder);
48         }
49     }
复制代码

解压进度就正常了很多,限制间隔只会优化单个文件解压过程中的进度,单个文件解压完成时最后还是有进度回调的。

再看看Zip压缩:

复制代码
 1     public static void Zip(string sourceFolder, string destZipFile, string password,
 2         Action<ZipProgress> zipProgressAction)
 3     {
 4         if (string.IsNullOrEmpty(destZipFile)) throw new ArgumentNullException(nameof(destZipFile));
 5         if (!destZipFile.ToUpper().EndsWith(".ZIP")) throw new ArgumentException("保存路径不是Zip文件", destZipFile);
 6         if (File.Exists(destZipFile)) File.Delete(destZipFile);
 7 
 8         using (var zipFile = new ZipFile())
 9         {
10             // 设置压缩进度事件处理程序
11             zipFile.SaveProgress += (sender, e) =>
12             {
13                 if (e.EventType == ZipProgressEventType.Saving_AfterWriteEntry)
14                     zipProgressAction?.Invoke(new ZipProgress(e.EntriesSaved, e.EntriesTotal, e.CurrentEntry.FileName));
15             };
16             zipFile.AddDirectory(sourceFolder);
17             zipFile.Password = password;
18             zipFile.Save(destZipFile);
19         }
20     }
复制代码

如果不考虑加密、压缩进度,DotNetZip压缩zip文件只需要几行代码,所以是相当的易学易用、入手快

还是同一个847M的zip文件,测试下解压缩性能,解压11907ms,压缩耗时16282ms,用数据说话性能强不强

用表格把这三个方案的对比列下:

所以如果你需要处理简单的ZIP压缩和解压任务,且不需要高级特性,建议使用System.IO.Compression

需要考虑解压缩性能比如公司的大文件OTA功能,需要减少业务的处理时间,推荐使用DotNetZip。DotNetZip也能提供高级特性,进度显示等。至于停止维护的状况可以忽然,有BUG大家可以在公司内或者github维护下这个组件代码

 

作者:唐宋元明清2188 出处:http://www.cnblogs.com/kybs0/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。

标签:解压,文件,string,zip,压缩,zipFile,new,NET
From: https://www.cnblogs.com/sexintercourse/p/18431131

相关文章

  • ASP.NET Core SignalR :学习消息通讯,实现一个消息通知
    ASP.NETCoreSignalR:学习消息通讯,实现一个消息通知  什么是SignalR    目前我用业余时间正在做一个博客系统,其中有个功能就是评论通知,就是假如A用户评论B用户的时候,如果B用户首页处于打开状态,那么就会提示B用户有未读消息。暂时用SignalR来实现这个功能。我也是......
  • 记.Net Framework中wwwroot文件限制用户访问
    背景项目.NetFramework做的,已经线上跑了很多年了,突然发现用户上传的文件都被放到了wwwroot//Content/Upload目录,这些文件都是比较重要的,程序用来读取解析数据的,但是被直接可以公开访问了。其实要改也很简单,代码改一下,文件挪一下位置就可以了,但是如果这样改就是一个线上大Bug裸......
  • 记.Net Core Host服务使用Dapper内存溢出问题
    背景项目要做一个数据迁移,牵扯大概60多张表,几千万数据,这些数据都被放到了csv文件中并拆分成了10w条记录一个文件。思路是使用.NetCoreHost开一个线程去读取文件夹csv根据业务导入到表就可以。ps:第一次用Dapper做这种处理问题在导入过程中,因为我们的服务器内存只有8G。经常......
  • 使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
    使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能 前言最近比较闲,(项目要转Java被分到架构组,边缘化人员,无所事事哈哈哈哈)记录一下前段时间用到的.NET框架下采用并行策略充分利用多核CPU进行优化的一个方法起因是项目中有个结算的方法,需要汇总一个月的数据......
  • C#|.net core 基础 - 深拷贝的五大类N种实现方式
    C#|.netcore基础-深拷贝的五大类N种实现方式 合集-C#|.netcore基础(6) 1.C#|.netcore基础-“hello”.IndexOf(“\0”,2)中的坑08-302.C#|.netcore基础-如何判断连续子序列09-033.C#|.netcore基础-值传递vs引用传递09-194.C#|.netcore基础-扩展数......
  • 一个.NET开源、快速、低延迟的异步套接字服务器和客户端库
    一个.NET开源、快速、低延迟的异步套接字服务器和客户端库 思维导航前言项目介绍主要特性功能组件使用示例基准测试项目源代码TCP聊天服务器示例项目源码地址优秀项目和框架精选前言最近有不少小伙伴在问:.NET有什么值得推荐的网络通信框架?今天大姚给大家分......
  • 【深度学习基础模型】径向基函数网络(Radial Basis Function Networks, RBFN)详细理解并
    【深度学习基础模型】径向基函数网络(RadialBasisFunctionNetworks,RBFN)【深度学习基础模型】径向基函数网络(RadialBasisFunctionNetworks,RBFN)文章目录【深度学习基础模型】径向基函数网络(RadialBasisFunctionNetworks,RBFN)1.算法原理介绍:径向基函数网络(R......
  • 使用.NET并行任务库(TPL)与并行Linq(PLINQ)充分利用多核性能
    前言最近比较闲,(项目要转Java被分到架构组,边缘化人员,无所事事哈哈哈哈)记录一下前段时间用到的.NET框架下采用并行策略充分利用多核CPU进行优化的一个方法起因是项目中有个结算的方法,需要汇总一个月的数据在内存中进行计算,统计,分组,然后产生新的数据在某个客户那部署......
  • modbus设备数据 转 profinet IO项目案例
    目录1案例说明12VFBOX网关工作原理13准备工作24设置网关采集MODBUS从站数据25用PROFINETIO协议转发数据86案例总结101案例说明设置网关采集Modbus设备数据把采集的数据转成profinetIO协议转发给其他系统。2VFBOX网关工作原理VFBOX网关是协议转换网关,是把一......
  • BACnet协议(1)
    1、BACnet介绍BACnet(BuildingAutomationandControlNetworks)是一种用于智能建筑的通信协议,它是由国际标准化组织(ISO)、美国国家标准协会(ANSI)和美国采暖、制冷与空调工程师学会(ASHRAE)定义的通信协议。主要用途包括:暖通空调(HVAC)控制:BACnet用于控制和监测暖通空调系统的运行......