最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)

   在项目开发中,除了对数据的展示更多的就是对文件的相关操作,例如文件的创建和删除,以及文件的压缩和解压。文件压缩的好处有很多,主要就是在文件传输的方面,文件压缩的好处就不需要赘述,因为无论是开发者,还是使用者对于文件压缩的好处都是深有体会。至于文件压缩的原理,在我的另一篇博客中有简单的介绍,在这里就不再做介绍,需要了解的可以查看。

   .NET在System.IO.Compression命名空间中提供了GZip、Defalate两种压缩算法。今天我要介绍的一种压缩组件是DotNetZip组件。

一.DotNetZip组件概述:

   在DotNetZip的自我介绍中号称是”DotNetZip是.NET最好的开源ZIP库“,至于是不是最好的压缩组件,在这里就不做评价,毕竟每个使用者的心态和工作环境不同,项目对组件的需求也不同,在选择组件的时候,就需要开发者自己衡量了。估计很多人还没有看到这里就开始在键盘上敲字吐槽了,标题是我借用官方对外的宣传口号,不用太在意这些细节。

   DotNetZip - Zip和解压缩在C#,VB,任何.NET语言都可使用。DotNetZip是一个FAST,免费类库和用于操纵zip文件的工具集。 使用VB,C#或任何.NET语言轻松创建,解压缩或更新zip文件。DotNetZip在具有完整.NET Framework的PC上运行,并且还在使用.NET Compact Framework的移动设备上运行。在VB,C#或任何.NET语言或任何脚本环境中创建和读取zip文件。

  DotNetZip组件的使用环境,毕竟软件的使用环境是每一个开发者都需要考虑的,这个世界没有绝对的好事,当然也没有绝对的坏事。接下来看一下其实用环境的说明吧:

  1.一个动态创建zip文件的Silverlight应用程序。

  2.一个ASP.NET应用程序,动态创建ZIP文件并允许浏览器下载它们。

  3.一个Windows服务,定期地为了备份和归档目的上拉一个目录。

  4.修改现有归档的WPF程序 - 重命名条目,从归档中删除条目或向归档中添加新条目。

  5.一个Windows窗体应用程序,用于为归档内容的隐私创建AES加密的zip存档。

  6.解压缩或拉链的SSIS脚本。

  7.PowerShell或VBScript中的一个管理脚本,用于执行备份和归档。

  8.WCF服务,接收作为附件的zip文件,并动态地将zip解压缩到流以进行分析。

  9.一个老式的ASP(VBScript)应用程序,通过COM接口为DotNetZIp生成一个ZIP文件。

  10.读取或更新ODS文件的Windows Forms应用程序。

  11.从流内容创建zip文件,保存到流,提取到流,从流读取。

  12.创建自解压档案。

   DotNetZip是一个100%的托管代码库,可用于任何.NET应用程序 - 控制台,Winforms,WPF,ASP.NET,Sharepoint,Web服务应用程序等。 新的v1.9.1.6:Silverlight。 它还可以从脚本环境或具有COM功能的环境(如Powershell脚本,VBScript,VBA,VB6,PHP,Perl,Javascript等)中使用。 无论使用什么环境,DotNetZip生成的zip文件可与Windows资源管理器以及Java应用程序,在Linux上运行的应用程序完全互操作。

    该组件设计简单,易于使用。 DotNetZip打包为一个单一的DLL,大小约400k。 它没有第三方依赖。 它是中等信任,因此可以在大多数托管商使用。 通过引用DLL来获取压缩。 该库支持zip密码,Unicode,ZIP64,流输入和输出,AES加密,多个压缩级别,自解压缩存档,跨区存档等。

   以上的一些描述来自与官网,就不再吹捧这个组件了,在这里需要说明的是在组件的选择和使用上,主要取决与项目的实际情况。详情见:http://dotnetzip.codeplex.com/

二.DotNetZip相关核心类和方法解析:

    由于下载的是DLL文件,还是采用.NET Reflector对DLL文件进行反编译,以此查看源代码。一下主要介绍一些类和方法,没有完全介绍,首先是由于篇幅所限,其实是完全没有必要,因为对于开发者而言,没有必要全部了解这些类,在实际的开发中,可以根据API进行对应的方法调用,这些技能应该是一个开发人员应该具备的。

   1.ZipFile类的AddEntry()、Save()和IsZipFile()方法:

public ZipEntry AddEntry(string entryName, WriteDelegate writer)
{
    ZipEntry ze = ZipEntry.CreateForWriter(entryName, writer);
    if (this.Verbose)
    {
        this.StatusMessageTextWriter.WriteLine("adding {0}...", entryName);
    }
    return this._InternalAddEntry(ze);
}
public void Save()
{
    try
    {
        bool flag = false;
        this._saveOperationCanceled = false;
        this._numberOfSegmentsForMostRecentSave = 0;
        this.OnSaveStarted();
        if (this.WriteStream == null)
        {
            throw new BadStateException("You haven't specified where to save the zip.");
        }
        if (((this._name != null) && this._name.EndsWith(".exe")) && !this._SavingSfx)
        {
            throw new BadStateException("You specified an EXE for a plain zip file.");
        }
        if (!this._contentsChanged)
        {
            this.OnSaveCompleted();
            if (this.Verbose)
            {
                this.StatusMessageTextWriter.WriteLine("No save is necessary....");
            }
        }
        else
        {
            this.Reset(true);
            if (this.Verbose)
            {
                this.StatusMessageTextWriter.WriteLine("saving....");
            }
            if ((this._entries.Count >= 0xffff) && (this._zip64 == Zip64Option.Default))
            {
                throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
            }
            int current = 0;
            ICollection<ZipEntry> entries = this.SortEntriesBeforeSaving ? this.EntriesSorted : this.Entries;
            foreach (ZipEntry entry in entries)
            {
                this.OnSaveEntry(current, entry, true);
                entry.Write(this.WriteStream);
                if (this._saveOperationCanceled)
                {
                    break;
                }
                current++;
                this.OnSaveEntry(current, entry, false);
                if (this._saveOperationCanceled)
                {
                    break;
                }
                if (entry.IncludedInMostRecentSave)
                {
                    flag |= entry.OutputUsedZip64.Value;
                }
            }
            if (!this._saveOperationCanceled)
            {
                ZipSegmentedStream writeStream = this.WriteStream as ZipSegmentedStream;
                this._numberOfSegmentsForMostRecentSave = (writeStream != null) ? writeStream.CurrentSegment : 1;
                bool flag2 = ZipOutput.WriteCentralDirectoryStructure(this.WriteStream, entries, this._numberOfSegmentsForMostRecentSave, this._zip64, this.Comment, new ZipContainer(this));
                this.OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
                this._hasBeenSaved = true;
                this._contentsChanged = false;
                flag |= flag2;
                this._OutputUsesZip64 = new bool?(flag);
                if ((this._name != null) && ((this._temporaryFileName != null) || (writeStream != null)))
                {
                    this.WriteStream.Dispose();
                    if (this._saveOperationCanceled)
                    {
                        return;
                    }
                    if (this._fileAlreadyExists && (this._readstream != null))
                    {
                        this._readstream.Close();
                        this._readstream = null;
                        foreach (ZipEntry entry2 in entries)
                        {
                            ZipSegmentedStream stream2 = entry2._archiveStream as ZipSegmentedStream;
                            if (stream2 != null)
                            {
                                stream2.Dispose();
                            }
                            entry2._archiveStream = null;
                        }
                    }
                    string path = null;
                    if (File.Exists(this._name))
                    {
                        path = this._name + "." + Path.GetRandomFileName();
                        if (File.Exists(path))
                        {
                            this.DeleteFileWithRetry(path);
                        }
                        File.Move(this._name, path);
                    }
                    this.OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
                    File.Move((writeStream != null) ? writeStream.CurrentTempName : this._temporaryFileName, this._name);
                    this.OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
                    if (path != null)
                    {
                        try
                        {
                            if (File.Exists(path))
                            {
                                File.Delete(path);
                            }
                        }
                        catch
                        {
                        }
                    }
                    this._fileAlreadyExists = true;
                }
                NotifyEntriesSaveComplete(entries);
                this.OnSaveCompleted();
                this._JustSaved = true;
            }
        }
    }
    finally
    {
        this.CleanupAfterSaveOperation();
    }
}
public static bool IsZipFile(Stream stream, bool testExtract)
{
    if (stream == null)
    {
        throw new ArgumentNullException("stream");
    }
    bool flag = false;
    try
    {
        if (!stream.CanRead)
        {
            return false;
        }
        Stream @null = Stream.Null;
        using (ZipFile file = Read(stream, null, null, null))
        {
            if (testExtract)
            {
                foreach (ZipEntry entry in file)
                {
                    if (!entry.IsDirectory)
                    {
                        entry.Extract(@null);
                    }
                }
            }
        }
        flag = true;
    }
    catch (IOException)
    {
    }
    catch (ZipException)
    {
    }
    return flag;
}

 2.Read()读取数据流:

private static ZipFile Read(Stream zipStream, TextWriter statusMessageWriter, Encoding encoding, EventHandler<ReadProgressEventArgs> readProgress)
{
    if (zipStream == null)
    {
        throw new ArgumentNullException("zipStream");
    }
    ZipFile zf = new ZipFile {
        _StatusMessageTextWriter = statusMessageWriter,
        _alternateEncoding = encoding ?? DefaultEncoding,
        _alternateEncodingUsage = ZipOption.Always
    };
    if (readProgress != null)
    {
        zf.ReadProgress += readProgress;
    }
    zf._readstream = (zipStream.Position == 0L) ? zipStream : new OffsetStream(zipStream);
    zf._ReadStreamIsOurs = false;
    if (zf.Verbose)
    {
        zf._StatusMessageTextWriter.WriteLine("reading from stream...");
    }
    ReadIntoInstance(zf);
    return zf;
}

   以上是对ZipFile类的一些方法的解析,提供了该组件的一些方法的源码,至于源码的解读上难度不是很大,至于该组件的API,可以在下载DLL文件后,可以直接查看相应的方法和属性,在这里就不做详细的介绍。

三.DotNetZip组件使用实例:

   以上是对该组件的一些解析,接下来我们看看实例:

1.压缩ZIP文件:

        /// <summary>
        /// 压缩ZIP文件
        /// 支持多文件和多目录,或是多文件和多目录一起压缩
        /// </summary>
        /// <param name="list">待压缩的文件或目录集合</param>
        /// <param name="strZipName">压缩后的文件名</param>
        /// <param name="isDirStruct">是否按目录结构压缩</param>
        /// <returns>成功:true/失败:false</returns>
        public static bool CompressMulti(List<string> list, string strZipName, bool isDirStruct)
        {
            if (list == null)
            {
                throw new ArgumentNullException("list");
            }
            if (string.IsNullOrEmpty(strZipName))
            {
                throw new ArgumentNullException(strZipName);
            }
            try
            {
                //设置编码,解决压缩文件时中文乱码
                using (var zip = new ZipFile(Encoding.Default))
                {
                    foreach (var path in list)
                    {
                        //取目录名称
                        var fileName = Path.GetFileName(path);
                        //如果是目录
                        if (Directory.Exists(path))
                        {
                            //按目录结构压缩
                            if (isDirStruct)
                            {
                                zip.AddDirectory(path, fileName);
                            }
                            else
                            {
                                //目录下的文件都压缩到Zip的根目录
                                zip.AddDirectory(path);
                            }
                        }
                        if (File.Exists(path))
                        {
                            zip.AddFile(path);
                        }
                    }
                    //压缩
                    zip.Save(strZipName);
                    return true;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }

2.解压ZIP文件:

        /// <summary>
        /// 解压ZIP文件
        /// </summary>
        /// <param name="strZipPath">待解压的ZIP文件</param>
        /// <param name="strUnZipPath">解压的目录</param>
        /// <param name="overWrite">是否覆盖</param>
        /// <returns>成功:true/失败:false</returns>
        public static bool Decompression(string strZipPath, string strUnZipPath, bool overWrite)
        {
            if (string.IsNullOrEmpty(strZipPath))
            {
                throw new ArgumentNullException(strZipPath);
            }
            if (string.IsNullOrEmpty(strUnZipPath))
            {
                throw new ArgumentNullException(strUnZipPath);
            }
            try
            {
                var options = new ReadOptions
                {
                    Encoding = Encoding.Default
                };
                //设置编码,解决解压文件时中文乱码
                using (var zip = ZipFile.Read(strZipPath, options))
                {
                    foreach (var entry in zip)
                    {
                        if (string.IsNullOrEmpty(strUnZipPath))
                        {
                            strUnZipPath = strZipPath.Split('.').First();
                        }
                        entry.Extract(strUnZipPath,overWrite
                                ? ExtractExistingFileAction.OverwriteSilently
                                : ExtractExistingFileAction.DoNotOverwrite);
                    }
                    return true;
                }
            }
            catch (Exception ex)
            {
               throw new Exception(ex.Message);
            }
        }

3.得到指定的输入流的ZIP压缩流对象:

        /// <summary>
        /// 得到指定的输入流的ZIP压缩流对象
        /// </summary>
        /// <param name="sourceStream">源数据流</param>
        /// <param name="entryName">实体名称</param>
        /// <returns></returns>
        public static Stream ZipCompress(Stream sourceStream, string entryName = "zip")
        {
            if (sourceStream == null)
            {
                throw new ArgumentNullException("sourceStream");
            }
            var compressedStream = new MemoryStream();      
            long sourceOldPosition = 0;
            try
            {
                sourceOldPosition = sourceStream.Position;
                sourceStream.Position = 0;
                using (var zip = new ZipFile())
                {
                    zip.AddEntry(entryName, sourceStream);
                    zip.Save(compressedStream);
                    compressedStream.Position = 0;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                try
                {
                    sourceStream.Position = sourceOldPosition;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
            return compressedStream;
        }

4.得到指定的字节数组的ZIP解压流对象:

        /// <summary>
        /// 得到指定的字节数组的ZIP解压流对象
        /// 当前方法仅适用于只有一个压缩文件的压缩包,即方法内只取压缩包中的第一个压缩文件
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static Stream ZipDecompress(byte[] data)
        {
            Stream decompressedStream = new MemoryStream();
            if (data == null) return decompressedStream;
            try
            {
                var dataStream = new MemoryStream(data);
                using (var zip = ZipFile.Read(dataStream))
                {
                    if (zip.Entries.Count > 0)
                    {
                        zip.Entries.First().Extract(decompressedStream);
                        // Extract方法中会操作ms,后续使用时必须先将Stream位置归零,否则会导致后续读取不到任何数据
                        // 返回该Stream对象之前进行一次位置归零动作
                        decompressedStream.Position = 0;
                    }
                }
            }
            catch(Exception ex)
            {
                throw new Exception(ex.Message);
            }
            return decompressedStream;
        }

四.总结:

   以上是对DotNetZip组件的一些解析和方法实例,至于这款组件是不是最好的.NET压缩组件,这个就不做评价。个人在选择组件的时候,首先考虑的是开源,其次是免费,最后再考虑效率和实用性,毕竟在国内的一些情况是所有开发者都清楚的(不提国外是由于我不知道国外的情况)。客户需要降低成本,并且组件要可以进行定制。不过个人认为收费应该是一种趋势,毕竟所有的产品都是需要人员进行维护和开发。以上的文章中有不足之处,还望多多指正。

.NET组件介绍系列:

    一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)

高效而稳定的企业级.NET Office 组件Spire(.NET组件介绍之二)

 最好的.NET开源免费ZIP库DotNetZip(.NET组件介绍之三)

免费开源的DotNet二维码操作组件ThoughtWorks.QRCode(.NET组件介绍之四)

免费开源的DotNet任务调度组件Quartz.NET(.NET组件介绍之五)

免费高效实用的Excel操作组件NPOI(.NET组件介绍之六)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏流柯技术学院

用Fiddler模拟低速网络环境

但当有人反应说「你的网页好慢」 甚至当网路速度慢,会造成你的网页跳出什么啊哩不哒的bug时要如何重现呢?

862
来自专栏Android源码框架分析

Android6.0权限适配及兼容库的实现

从6.0 MarshMallow开始,Android支持动态权限管理,即有些权限需要在使用到的时候动态申请,根据用户的选择需要有不同的处理,具体表现可以看下图:

1222
来自专栏Flutter入门

Android PC投屏简单尝试

代码地址 :https://github.com/deepsadness/MediaProjectionDemo

1545
来自专栏ml

关于docker 意外停止,重新快速启动措施

1597
来自专栏即时通讯技术

手把手教你读取Android版微信和手Q的聊天记录(仅作技术研究学习)

特别说明:本文内容仅用于即时通讯技术研究和学习之用,请勿用于非法用途。如本文内容有不妥之处,请联系JackJiang进行处理!

582
来自专栏c#开发者

datagrid资料+ by iCeSnaker - Program rhapsody

datagrid资料+ by iCeSnaker - Program rhapsody 关于datagrid的打印 http://www.chinaaspx.c...

3369
来自专栏walterlv - 吕毅的博客

实现一个 WPF 版本的 ConnectedAnimation

2017-12-25 11:44

340
来自专栏Android群英传

Android multidex 主dex是怎么来的?

1532
来自专栏iOS开发攻城狮的集散地

iOS app国际化 、跳转到系统设置、iOS10通知、正则表达式

1384
来自专栏黑白安全

windows命令执行漏洞不会玩? 看我!

经常有小伙伴碰到了命令执行漏洞不会玩,比如mssql注入点的命令执行,怎么来获取一个meterpreter?这个时候,就需要想办法来获取了,关于命令行来执行远程...

934

扫码关注云+社区