详解最好的.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在具有完整.NETFramework的PC上运行,并且还在使用.NETCompactFramework的移动设备上运行。在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文件的WindowsForms应用程序。
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文件,还是采用.NETReflector对DLL文件进行反编译,以此查看源代码。一下主要介绍一些类和方法,没有完全介绍,首先是由于篇幅所限,其实是完全没有必要,因为对于开发者而言,没有必要全部了解这些类,在实际的开发中,可以根据API进行对应的方法调用,这些技能应该是一个开发人员应该具备的。
1.ZipFile类的AddEntry()、Save()和IsZipFile()方法:
publicZipEntryAddEntry(stringentryName,WriteDelegatewriter)
{
ZipEntryze=ZipEntry.CreateForWriter(entryName,writer);
if(this.Verbose)
{
this.StatusMessageTextWriter.WriteLine("adding{0}...",entryName);
}
returnthis._InternalAddEntry(ze);
}
publicvoidSave()
{
try
{
boolflag=false;
this._saveOperationCanceled=false;
this._numberOfSegmentsForMostRecentSave=0;
this.OnSaveStarted();
if(this.WriteStream==null)
{
thrownewBadStateException("Youhaven'tspecifiedwheretosavethezip.");
}
if(((this._name!=null)&&this._name.EndsWith(".exe"))&&!this._SavingSfx)
{
thrownewBadStateException("YouspecifiedanEXEforaplainzipfile.");
}
if(!this._contentsChanged)
{
this.OnSaveCompleted();
if(this.Verbose)
{
this.StatusMessageTextWriter.WriteLine("Nosaveisnecessary....");
}
}
else
{
this.Reset(true);
if(this.Verbose)
{
this.StatusMessageTextWriter.WriteLine("saving....");
}
if((this._entries.Count>=0xffff)&&(this._zip64==Zip64Option.Default))
{
thrownewZipException("Thenumberofentriesis65535orgreater.ConsidersettingtheUseZip64WhenSavingpropertyontheZipFileinstance.");
}
intcurrent=0;
ICollection<ZipEntry>entries=this.SortEntriesBeforeSaving?this.EntriesSorted:this.Entries;
foreach(ZipEntryentryinentries)
{
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)
{
ZipSegmentedStreamwriteStream=this.WriteStreamasZipSegmentedStream;
this._numberOfSegmentsForMostRecentSave=(writeStream!=null)?writeStream.CurrentSegment:1;
boolflag2=ZipOutput.WriteCentralDirectoryStructure(this.WriteStream,entries,this._numberOfSegmentsForMostRecentSave,this._zip64,this.Comment,newZipContainer(this));
this.OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
this._hasBeenSaved=true;
this._contentsChanged=false;
flag|=flag2;
this._OutputUsesZip64=newbool?(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(ZipEntryentry2inentries)
{
ZipSegmentedStreamstream2=entry2._archiveStreamasZipSegmentedStream;
if(stream2!=null)
{
stream2.Dispose();
}
entry2._archiveStream=null;
}
}
stringpath=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();
}
}
publicstaticboolIsZipFile(Streamstream,booltestExtract)
{
if(stream==null)
{
thrownewArgumentNullException("stream");
}
boolflag=false;
try
{
if(!stream.CanRead)
{
returnfalse;
}
Stream@null=Stream.Null;
using(ZipFilefile=Read(stream,null,null,null))
{
if(testExtract)
{
foreach(ZipEntryentryinfile)
{
if(!entry.IsDirectory)
{
entry.Extract(@null);
}
}
}
}
flag=true;
}
catch(IOException)
{
}
catch(ZipException)
{
}
returnflag;
}
2.Read()读取数据流:
privatestaticZipFileRead(StreamzipStream,TextWriterstatusMessageWriter,Encodingencoding,EventHandler<ReadProgressEventArgs>readProgress)
{
if(zipStream==null)
{
thrownewArgumentNullException("zipStream");
}
ZipFilezf=newZipFile{
_StatusMessageTextWriter=statusMessageWriter,
_alternateEncoding=encoding??DefaultEncoding,
_alternateEncodingUsage=ZipOption.Always
};
if(readProgress!=null)
{
zf.ReadProgress+=readProgress;
}
zf._readstream=(zipStream.Position==0L)?zipStream:newOffsetStream(zipStream);
zf._ReadStreamIsOurs=false;
if(zf.Verbose)
{
zf._StatusMessageTextWriter.WriteLine("readingfromstream...");
}
ReadIntoInstance(zf);
returnzf;
}
以上是对ZipFile类的一些方法的解析,提供了该组件的一些方法的源码,至于源码的解读上难度不是很大,至于该组件的API,可以在下载DLL文件后,可以直接查看相应的方法和属性,在这里就不做详细的介绍。
三.DotNetZip组件使用实例:
以上是对该组件的一些解析,接下来我们看看实例:
1.压缩ZIP文件:
///<summary>
///压缩ZIP文件
///支持多文件和多目录,或是多文件和多目录一起压缩
///</summary>
///<paramname="list">待压缩的文件或目录集合</param>
///<paramname="strZipName">压缩后的文件名</param>
///<paramname="isDirStruct">是否按目录结构压缩</param>
///<returns>成功:true/失败:false</returns>
publicstaticboolCompressMulti(List<string>list,stringstrZipName,boolisDirStruct)
{
if(list==null)
{
thrownewArgumentNullException("list");
}
if(string.IsNullOrEmpty(strZipName))
{
thrownewArgumentNullException(strZipName);
}
try
{
//设置编码,解决压缩文件时中文乱码
using(varzip=newZipFile(Encoding.Default))
{
foreach(varpathinlist)
{
//取目录名称
varfileName=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);
returntrue;
}
}
catch(Exceptionex)
{
thrownewException(ex.Message);
}
}
2.解压ZIP文件:
///<summary>
///解压ZIP文件
///</summary>
///<paramname="strZipPath">待解压的ZIP文件</param>
///<paramname="strUnZipPath">解压的目录</param>
///<paramname="overWrite">是否覆盖</param>
///<returns>成功:true/失败:false</returns>
publicstaticboolDecompression(stringstrZipPath,stringstrUnZipPath,booloverWrite)
{
if(string.IsNullOrEmpty(strZipPath))
{
thrownewArgumentNullException(strZipPath);
}
if(string.IsNullOrEmpty(strUnZipPath))
{
thrownewArgumentNullException(strUnZipPath);
}
try
{
varoptions=newReadOptions
{
Encoding=Encoding.Default
};
//设置编码,解决解压文件时中文乱码
using(varzip=ZipFile.Read(strZipPath,options))
{
foreach(varentryinzip)
{
if(string.IsNullOrEmpty(strUnZipPath))
{
strUnZipPath=strZipPath.Split('.').First();
}
entry.Extract(strUnZipPath,overWrite
?ExtractExistingFileAction.OverwriteSilently
:ExtractExistingFileAction.DoNotOverwrite);
}
returntrue;
}
}
catch(Exceptionex)
{
thrownewException(ex.Message);
}
}
3.得到指定的输入流的ZIP压缩流对象:
///<summary>
///得到指定的输入流的ZIP压缩流对象
///</summary>
///<paramname="sourceStream">源数据流</param>
///<paramname="entryName">实体名称</param>
///<returns></returns>
publicstaticStreamZipCompress(StreamsourceStream,stringentryName="zip")
{
if(sourceStream==null)
{
thrownewArgumentNullException("sourceStream");
}
varcompressedStream=newMemoryStream();
longsourceOldPosition=0;
try
{
sourceOldPosition=sourceStream.Position;
sourceStream.Position=0;
using(varzip=newZipFile())
{
zip.AddEntry(entryName,sourceStream);
zip.Save(compressedStream);
compressedStream.Position=0;
}
}
catch(Exceptionex)
{
thrownewException(ex.Message);
}
finally
{
try
{
sourceStream.Position=sourceOldPosition;
}
catch(Exceptionex)
{
thrownewException(ex.Message);
}
}
returncompressedStream;
}
4.得到指定的字节数组的ZIP解压流对象:
///<summary>
///得到指定的字节数组的ZIP解压流对象
///当前方法仅适用于只有一个压缩文件的压缩包,即方法内只取压缩包中的第一个压缩文件
///</summary>
///<paramname="data"></param>
///<returns></returns>
publicstaticStreamZipDecompress(byte[]data)
{
StreamdecompressedStream=newMemoryStream();
if(data==null)returndecompressedStream;
try
{
vardataStream=newMemoryStream(data);
using(varzip=ZipFile.Read(dataStream))
{
if(zip.Entries.Count>0)
{
zip.Entries.First().Extract(decompressedStream);
//Extract方法中会操作ms,后续使用时必须先将Stream位置归零,否则会导致后续读取不到任何数据
//返回该Stream对象之前进行一次位置归零动作
decompressedStream.Position=0;
}
}
}
catch(Exceptionex)
{
thrownewException(ex.Message);
}
returndecompressedStream;
}
四.总结:
以上是对DotNetZip组件的一些解析和方法实例,至于这款组件是不是最好的.NET压缩组件,这个就不做评价。个人在选择组件的时候,首先考虑的是开源,其次是免费,最后再考虑效率和实用性,毕竟在国内的一些情况是所有开发者都清楚的(不提国外是由于我不知道国外的情况)。客户需要降低成本,并且组件要可以进行定制。不过个人认为收费应该是一种趋势,毕竟所有的产品都是需要人员进行维护和开发。以上的博文中有不足之处,还望多多指正。