SharpZipLib 或 DotNetZip...您应该选择哪一个?
对这两个免费的 zip 库进行比较,并为每个库提供了一个多线程 VB.NET 类包装器。
引言
这个示例项目附带了两个您可能感兴趣的类 - clsSharpZipLib
和 clsDotNetZip
。它们是这两个库核心功能的包装器 - 因为它们试图做到用户友好(特别是 DotNetZip),但使用它们仍然需要相当多的代码。如果您和我一样,您不想在每个项目中都一遍又一遍地重写它。而且它们是阻塞的,所以如果您不想锁定您的 UI,您需要创建一些线程……然后还有跟踪进度的问题。
这些类可以处理所有这些。它们提供了一个方便的回调,您可以使用它来跟踪进度、获取错误消息等。此示例项目显示了如何使用所有这些。
尽管如此,这并不是本文的重点。本文是对这两个 zip 库的比较,因为似乎虽然对它们有很多看法,但我发现的数据要么具有误导性,要么就是完全错误的。使用此示例项目,您将能够自行确定哪个库适合您。
背景
最近,我发现自己需要为我正在处理的项目添加有限的 zip 功能。我的应用程序需要尽可能快地 zip 文件,但它将永远不会被要求打开它创建的 zip 文件。多年前,我为 #ZipLib
编写了一个包装器,然后我挖出了它——并懊恼地看着我当初的成果……它需要重写。
于是我发现自己重写了这个包装器……并努力弄清楚为什么 #ZipLib
如此缓慢。起初,我认为是我的代码有问题,但在谷歌搜索解决方案时,我发现很多人都在问同样的问题。似乎 #ZipLib
就是这样。如果您使用它来压缩 zip 文件(而不是在 zip 存档中添加未压缩的文件),预计即使在最低压缩级别下,它的速度也会比 winrar 或 winzip 慢一倍。
我发现了一些关于 DotNetZip 的评论 - 这是另一个免费解决方案,令人失望的是,尽管人们认为它更容易使用,但它速度稍慢。幸运的是,事实并非如此。
事实上,我最终在 Stack Overflow 上找到一个页面,其中一位发帖者谈到了 DotNetZip 的 ParallelDeflateOutputStream
类。该类使用多线程压缩文件,通过利用您系统中的所有核心来提高压缩速度。
经过大量的测试,我构建的用于完善和测试我最终将使用的包装器类的项目就是这个 - 一个旨在比较每个库性能的应用程序。
如果您选择运行此应用程序,您将能够创建 zip 文件,列出其内容(以一种原始的方式),选择要提取的文件,设置压缩级别(如果您选择使用压缩),设置密码,选择使用 Zip64(或不使用),最重要的是,通过单击复选框来选择使用哪个库来执行您的 zip 操作。
每个操作都会计时,并在 zip/unzip 完成后显示结果。
我在我的 Windows 7 x64 四核笔记本电脑上开发了这个测试应用程序。它有 4GB 内存和 5400 RPM 的硬盘。在我的测试过程中,我使用了一个包含近 7000 个文件、总计 873MB 的源文件夹。
在这台机器上,DotNetZip 在压缩大文件时使用了所有 4 个核心,并且压缩我的测试源文件夹所需的时间不到 #zipLib 的一半。实际的平均时间是:
#ZipLib
:压缩级别 1,创建 684 MB 文件,完成时间为 1 分钟 45 秒。DotNetZip
:压缩级别 1,创建 690 MB 文件,完成时间为 45 秒。
我认为值得注意的是,WinRar 平均需要 1 分 5 秒来压缩这些相同的文件。
在提取这些文件时,#ZipLib
比 DotNetZip 快大约 15 秒。
同样,这些是平均时间。我很难在这里为 #ZipLib
记录 1 分 45 秒,因为在某些测试运行中,它实际上花费了近 3 分钟。以上时间是在反复 zip 和提取后观察到的,当时 Windows 的文件缓存已尽可能好地工作。
我意识到我的测试只是我的测试,在我的硬件上运行,这些数字在其他地方会有所不同。
如果您有兴趣,我邀请您下载示例项目并进行自己的测试。如果您选择在此处发布您的结果,我们将对这两个库的对比有更好的了解。
我意识到来到此页面的大多数人可能是为了找到一种快速简便的方法来为他们的 VB.NET 应用程序添加 zip 功能 - 因此我花时间将每个库的功能分离到自己的包装器类中。如果您决定要使用 DotNetZip,只需将 clsDotNetZip 复制到您的项目中,添加对 Ionic.zip.dll 的引用,看看示例项目(或下方 - 它非常简单)了解如何实现它,然后您就可以开始了。对于 #ZipLib
,它是 clsSharpzipLib
和 ICSharpCode.SharpZipLib.dll。
Using the Code
实例化
实例化一个类如下:
Dim zipLib As clsSharpZipLib = New clsSharpZipLib(zipPath, _
clsSharpZipLib.ZipAccessFlags.Create, _
1024 * 1024, _
nudCompression.Value, _
cbZip64.Checked, _
tbPassword.Text, _
100, _
AddressOf zipCallback)
现在,我认为这相当直接。然后我写了这个类,我当然会这么认为,所以我会解释我们在这里有什么。
zipPath
- 是一个string
,包含您要打开的 zip 文件的路径,或您要创建的 zip 文件的位置。clsSharpZipLib.ZipAccessFlags.Create
- 这是一个public enum
,您可以在类中找到。它是一个文件访问标志 - 它告诉类您将如何处理 zip 文件。1024 * 1024
- 这是您希望此类工作的缓冲区大小。nudCompression.Value
- 这是我在示例项目中用于指定压缩级别的数字上下控件。有效值为 0 - 9。cbZip64.Checked
- 是的 - 这是一个复选框控件。如果您勾选了它,那么您将使用 Zip64 进行压缩。tbPassword.Text
- 不言自明。100
- 这是毫秒单位的回调更新速度。我在这里传递值 100,所以回调在这个示例项目中将每 100 毫秒触发一次,包含您可以用来更新用户界面有关当前操作的数据。AddressOf zipCallback
-zipCallback
是示例项目中回调Sub
的地址。所有好东西都在 UI 线程之外发生,所以如果您想获取有关您的 zip 操作进展的信息,您必须提供这样一个回调。
在示例项目中,我跟踪字节为单位的总进度,以及正在处理的当前文件的字节为单位的进度。这样做似乎是正确的方式,并能实现平滑准确的进度条。
回调
这就是示例应用程序中的回调 sub
的样子:
Private Sub zipCallback(ByRef zipData As clsCompareLibs.ZipData)
Static lastName As String
If Me.InvokeRequired Then
Me.Invoke(callback, zipData)
Else
With zipData
If .fileList IsNot Nothing AndAlso .fileList.Count > 0 Then
' We've received a list of files. Add them to the listbox.
Dim names As New List(Of String)
currentEntries.Clear()
For Each entry As clsCompareLibs.ShortEntry In .fileList
names.Add(entry.name)
currentEntries.Add(entry)
Next
Me.lbFileList.Items.AddRange(names.ToArray())
Me.lblFileName.Text = "Complete."
Try
zipLib.Close()
Catch ex As Exception
End Try
me.Cursor = System.Windows.Forms.Cursors.Default
Else
' We're updating the UI with progress data here.
' Have we moved on to a new file?
If lastName <> zipData.currentFileName Then
' If so, set the progress bar to 0.
pbCurrentFile.Value = 0
lastName = zipData.currentFileName
End If
lblFileName.Text = .operationTitle
If .currentFileName <> "" Then lblFileName.Text += ": ...\" & _
Path.GetFileName(.currentFileName)
If .currentFileBytesCopied > 0 AndAlso .totalBytes > 0 Then
pbCurrentFile.Value = (.currentFileBytesCopied / .currentFileLength) * 100
pbTotalBytes.Value = (.totalBytesCopied / .totalBytes) * 100
pbCurrentFile.Refresh()
pbTotalBytes.Refresh()
End If
If .complete Then
zipLib.Close()
If .cancel Then
lblFileName.Text = "Canceled."
pbCurrentFile.Value = 0
pbTotalBytes.Value = 0
Else
endTime = Now
If endTime.Subtract(startTime).TotalSeconds > 60 then
lblFileName.Text = "Complete. This operation took " & _
endTime.Subtract(startTime).Minutes.ToString() & _
" minutes, and " & endTime.Subtract(startTime).Seconds.ToString() _
& " seconds."
Else
lblFileName.Text = "Complete. This operation took " & _
endTime.Subtract(startTime).TotalSeconds.ToString("N1") & _
" seconds."
End If
End If
tsbZipFiles.Visible = True
tsbZipFiles.Enabled = True
tsbListZipEntries.Visible = True
tsbCancel.Visible = False
me.Cursor = System.Windows.Forms.Cursors.Default
End If
If .errorMessage <> "" Then
MsgBox("" & .errorMessage, MsgBoxStyle.Critical, "Zip Example App")
me.Cursor = System.Windows.Forms.Cursors.Default
End If
End If
End With
End If
End Sub
您会看到,每次回调触发时,您都会获得当前操作的状态。您需要的所有用于跟踪进度的信息都在这里 - 当前文件的名称、该文件当前传输的字节数、正在复制的总字节数、当前总传输量、操作的标题(例如:“正在提取”或“正在压缩”),错误消息等。
指定要压缩/解压缩的文件
我试图使类接口尽可能像一个通用列表。也就是说,要将文件添加到 zip 文件,您可以使用 Add()
方法。Add()
将接受一个 string
或一个通用的 list(Of string)
。您可以将其传递给单个文件的路径,或每个条目中的文件夹。如果您传递一个文件夹,您还可以指定是否要递归子目录。
要获取 zip 文件中的条目列表,请使用 ListZipEntries()
。ListZipEntries()
方法不返回列表。这个类中的所有操作都在 UI 线程之外进行。列表在回调中返回。请参阅上面的说明如何检索它。
要从 zip 文件中提取文件,您可以调用 - 您猜对了 - Extract()
。Extract()
方法有三个重载:您可以传递一个包含要提取的条目的单个 string
和目标文件夹,一个包含要提取的条目的 list(Of String)
和目标文件夹,或者一个 List(Of ShortEntry)
和一个包含目标文件夹的 string
。
List(Of ShortEntry)
是您调用 ListZipEntries() 时获得的。ShortEntries 只是包含条目名称、大小和索引的结构。将 List(Of ShortEntry)
传递给 Extract()
将提高性能。
但是,没有更多了吗?
我敢肯定您知道这些库不仅可以 zip 和 unzip 文件。仅 DotNetZip 就可以创建自解压 zip 文件、将 zip 文件分成多个部分,等等。我没有试图包装每个库的所有功能……这需要我花费很长时间 - 并且记住,一开始我只需要一种快速 zip 一些文件的方法来处理我当前的项目。如果您需要比这更多的功能,您需要自己添加。
关注点
这两个库以及本文的意义可能存在疑问。作为 .NET 4.5 的一部分,Microsoft 将 ZipArchive
类作为框架的一部分包含在内。我快速查看了 MSDN,截至撰写本文时,它远不如 DotNetZip 灵活,尽管我相信这会随着时间而改变。让这些库保持相关性的可能是微软不提供的卓越性能和功能,尽管我认为这将使 #ZipLib
被淘汰,因为它的压缩性能不佳,并且似乎已被开发者放弃。
我想我们拭目以待。
历史
- 2012/08/04 - 修复了在未选择 zip 文件时点击“列出 zip 条目”按钮导致的崩溃问题。
- 2012/08/05 - 使用 `explicit` 和 `strict` 选项重新构建了项目。