欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-02-15 20:19:31
在项目开发中,除了对数据的展示更多的就是对文件的相关操作,例如文件的创建和删除,以及文件的压缩和解压。文件压缩的好处有很多,主要就是在文件传输的方面,文件压缩的好处就不需要...

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

 .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加密,多个压缩级别,自解压缩存档,跨区存档等。

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

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