本文为大家介绍下.NET解压/压缩zip文件。虽然解压缩不是啥核心技术,但压缩性能以及进度处理还是需要关注下,针对使用较多的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

需要考虑解压缩性能,推荐使用DotNetZip。至于停止维护的状况可以忽然,有BUG大家可以在公司内或者github维护下这个组件代码

.NET 压缩/解压文件的更多相关文章

  1. 通过SharpZipLib来压缩解压文件

    在项目开发中,一些比较常用的功能就是压缩解压文件了,其实类似的方法有许多 ,现将通过第三方类库SharpZipLib来压缩解压文件的方法介绍如下,主要目的是方便以后自己阅读,当然可以帮到有需要的朋友更 ...

  2. .NET使用ICSharpCode.SharpZipLib压缩/解压文件

    SharpZipLib是国外开源加压解压库,可以方便的对文件进行加压/解压 1.下载ICSharpCode.SharpZipLib.dll,并复制到bin目录下 http://www.icsharpc ...

  3. huffman压缩解压文件【代码】

    距离上次写完哈夫曼编码已经过去一周了,这一周都在写huffman压缩解压,哎,在很多小错误上浪费了很多时间调bug.其实这个程序的最关键部分不是我自己想的,而是借鉴了某位园友的代码,但是,无论如何,自 ...

  4. 【转载】.NET压缩/解压文件/夹组件

    转自:http://www.cnblogs.com/asxinyu/archive/2013/03/05/2943696.html 阅读目录 1.前言 2.关于压缩格式和算法的基础 3.几种常见的.N ...

  5. C#使用SharpZipLib压缩解压文件

    #region 加压解压方法 /// <summary> /// 功能:压缩文件(暂时只压缩文件夹下一级目录中的文件,文件夹及其子级被忽略) /// </summary> // ...

  6. linux压缩解压文件

    首先进入文件夹 cd /home/ftp2/1520/web 压缩方法一:压缩web下的888.com网站 zip -r 888.com.zip888.com 压缩方法二:将当前目录下的所有文件和文件 ...

  7. Freebsd下压缩解压文件详解

    压缩篇: 把/usr/webgames目录下的文件打包.命名为bak.tar.gz 放到/usr/db-bak目录里 下面命令可以在任意目录执行.无视当前目录和将要存放文件的目录.tar -zcvf ...

  8. 跨平台的zip文件压缩处理,支持压缩解压文件夹

    根据minizip改写的模块,需要zlib支持 输出的接口: #define RG_ZIP_FILE_REPLACE 0 #define RG_ZIP_FILE_APPEND 1 //压缩文件夹目录, ...

  9. tar压缩解压文件

    查看visualization1.5.tar.gz 压缩包里面的内容: $ tar -tf visualization1.5.tar.gz 解压指定文件JavascriptVisualRelease/ ...

  10. Ubuntu下压缩解压文件

    一般来说ubuntu 下带有tar 命令,可以用来解压和压缩之用.但是我们经常要与win下用户打交道,所以要安装一些解压工具如:rar zip 等命令. 如果要需要用到zip工具那么可以: sudo ...

随机推荐

  1. Mybatis 一级缓存

    Mybatis一级缓存介绍 什么是缓存 程序经常要调用的对象存在内容中,方法其使用时可以快速调用,不必去数据库或者其他持久化设备中查询,主要就是提高性能 Mybatis一级缓存 简介:一级缓存的作用域 ...

  2. 机器学习策略篇:详解数据分布不匹配时,偏差与方差的分析(Bias and Variance with mismatched data distributions)

    详解数据分布不匹配时,偏差与方差的分析 估计学习算法的偏差和方差真的可以帮确定接下来应该优先做的方向,但是,当训练集来自和开发集.测试集不同分布时,分析偏差和方差的方式可能不一样,来看为什么. 继续用 ...

  3. 2022 开源之夏 | Curve 邀你与中国存储软件共成长,赢万元奖金

    Curve 社区携手开源之夏,邀你开展有趣而精彩的开源之旅,直面社区大咖,积累项目经历,摘取丰厚奖金,共同推进我国基础软件自主创新 2022 年,Curve 社区再次加入系列高校开源活动 -- 开源之 ...

  4. Vue3 之 reactive、ref、toRef、toRefs 使用与区别,源码分析详细注释

    目录 reactive.ref.toRef.toRefs 使用与区别 reactive ref 作用及用法 toRef 作用及用法 toRefs 作用及用法 ref,toRef,toRefs 源码实现 ...

  5. Vue查询传参

    通过修改 getWK005 函数来实现这一点.这里的 query 参数就是发送 GET 请求时的查询参数.你可以将需要的条件作为 query 对象的属性传递进去.比如,如果你想要按照特定的条件查询信息 ...

  6. OpenGL 4.0中数据缓冲VBO,VAO,EBO的使用总结

    Opengl是大家常用的一个API,我们用它绘制数据的时候需要使用vao,vbo,ebo等对象,绘制方式分为 vao绘制,ebo绘制等.使用不同api还能分为普通调用以及Instance绘制. 首先申 ...

  7. 实验6-使用TensorFlow完成线性回归 cannot import name ‘OrderedDict‘ from ‘typing‘错误的解决方法

    找到对应的报错方法 删除 再添加from typing_extensions import OrderedDict

  8. mysql数据库无法录入汉字问题

    1.插入数据出现错误 show full columns from 表名;//查看数据表列编码 2. alter table 表名 change 列名 列名 varchar(自己设置) charact ...

  9. 【Uni-App】API笔记 P1

    1.调试打印: console.log() 向控制台打印 log 日志 console.debug() 向控制台打印 debug 日志 注:App 端自定义组件模式下,debug 方法等同于 log ...

  10. 中国的AI领域发展的重大不足 —— 数据缺少,尤其是自然语言领域(NLP)、大模型领域

    全世界公开可用的语言文本中绝大部分是英文文本,其中中文文本只有1.5% 相关: China's Betting Big On Artificial Intelligence. Could The US ...