在隱藏檔或者通過網路傳送檔案時,使用 Zip 壓縮可以節省空間的和網路頻寬。此外,還不會丟失經過 Zip 的檔案夾的目錄結構,這使其成為非常有用的壓縮方案。C# 語言不具有任何使您可以操縱 Zip 檔案的類,但是由於面向 .NET 的語言可以共用類實現,並且 J# 在 java.util.zip 命名空間中公開了類,因此您可以在 C# 代碼中使用這些類。本文將解釋如何使用 Microsoft J# 類庫建立能夠壓縮和解壓縮 Zip 檔案的 C# 應用程式。它還將介紹 J# 運行庫的其他一些可以從任何 .NET 相容語言中使用以節省某些編碼工作的獨特部分。
本頁內容
Zip 是一種受人歡迎的資料轉送和儲存標準,因為它可以節省磁碟空間和網路頻寬。典型的文本和資料庫檔案可以被壓縮至它們原始大小的 10%。即使二進位檔案不能進行同樣的壓縮,通常也可以獲得 50% 的壓縮比。
Zip 檔案的一個附加優點是單個檔案可以包含多個檔案,同時可以保留目錄結構。這使您可以發送附加到電子郵件訊息中的完整分類樹,並且讓收件者恢複原始檔案結構。
Zip 資料格式是開放的,並且不會涉及專利權或其他法律問題。開發人員可以自由地建立操縱 Zip 檔案的應用程式,以及使用低層級 Zip 壓縮演算法來暫時減小他們自己的自訂資料的大小。Zip 資料規範的作者在名為 zlib 的庫 (http://www.gzip.org/zlib) 中向開發人員提供壓縮和解壓縮演算法。Java 平台在 Java 開發套件 (JDK) 的版本 1.1 中採用了該庫,以構成 Java 存檔 (JAR) 檔案格式的基礎,因此從 JDK 版本 1.1 開始,標準 Java 語言 API 就包含了操縱 Zip 檔案所需的類。可以在 java.util.zip 命名空間下找到這些類。
Zip 檔案和 C#
我希望在用 C# 編寫的應用程式中使用 Zip 壓縮。遺憾的是,Microsoft.NET Framework 當前不包含任何用於操縱 Zip 檔案的類。但是,我的確找到了幾個與 Zip 壓縮有關的產品。例如,#ziplib(以前稱為 NZipLib,http://www.icsharpcode.net/OpenSource/SharpZipLib/default.asp)是 zlib 庫到 C# 的移植產品。它的許可證允許開發人員在封閉原始碼的商務應用程式中包含該庫。但是,在 MSDN Magazine 付印之時,#ziplib 尚處於預發布狀態(版本 0.31)。
另外一個解決方案是使用非託管 zlib 作為 Windows DLL 並且為其編寫必要的 Interop 封裝,但是由於壓縮涉及到在每個函數調用期間到處傳遞大量資料,因此編寫 Interop 封裝以獲得最佳效能將是一個困難的過程。儘管可以使用其他庫,但它們不是免費的。
返回頁首
解決方案
.NET Framework 的設計考慮了語言互通性。可以從任何實現了必要功能的 .NET 相容程式設計語言中正確地使用所有遵循某些特定規則的託管組件。互通性所需的規則和語言功能集稱為Common Language Specification (CLS)。
Microsoft 實現的所有 .NET 語言編譯器都是符合 CLS 規範 的,其中包括 Microsoft Visual J# .NET — 一種供希望在 Microsoft .NET Framework 上產生應用程式和服務的 Java 語言開發人員使用的開發工具。(Visual J# .NET 是由 Microsoft 獨立開發的。它沒有經過 Sun Microsystems, Inc. 的認可和批准)這就是為什麼可以在用 J# 編寫的 Windows 表單和 ASP.NET 應用程式中使用 .NET Framework 類的原因。
SharpZip 應用程式在解壓縮檔案時所做的第一件事情,是提示使用者指定應當在其中建立檔案的目錄。您可能已經注意到,應用程式顯示了“Browse for Folder”對話方塊。我傾向於使用 System.Windows.Forms.Design.FolderNameEditor.FolderBrowser 類,但是文檔聲稱該類型支援 .NET Framework 基礎結構,並且不適合直接使用,因此我通過匯入 Microsoft Shell Controls and Automation 類型庫,藉助於 COM Interop 來使用 Shell32 對象。
從 Zip 檔案中提取原始檔案(解壓縮)的操作非常簡單:只需調用 ZipFile 對象上的 getInputStream,並傳遞您要為其獲得壓縮檔的條目即可。GetInputStream 方法將產生一個 InputStream,以便您從中讀取存檔條目的內容。
ExtractZipFile Helper 函數為您完成該工作。通過使用單獨的條目將目錄存放在 Zip 檔案中,但每個條目中的檔案名稱也包含目錄資訊,因此 ExtractZipFile 忽略了目錄條目,並且從檔案名稱中提取必要的路徑資訊。
CreateEmptyZipFile Helper 函數建立一個 Zip 檔案並且立即關閉它。結果得到一個不含任何條目的空 Zip 檔案。追加或刪除項就沒有那麼簡單了,因為 java.util.zip 包不提供對 Zip 檔案的隨機訪問。對於刪除檔案,應當將想要保留的條目複製到新的 Zip 檔案。對於添加檔案,應當將所有條目複製到新的 Zip 檔案,然後追加新條目。複製條目涉及到按照我已經描述的方式從源檔案中解壓縮條目,然後將其重新壓縮到目標檔案。
private int GetWin32IntConstant(string name)
{
System.Reflection.Assembly asm =
System.Reflection.Assembly.GetAssembly(typeof(com.ms.win32.wina));
Type t = asm.GetType("com.ms.win32.win" + char.ToLower(name[0]),
true);
System.Reflection.FieldInfo info = t.GetField(name);
return int.Parse(info.GetValue(null).ToString());
}
使用該技術檢索 Windows API 常量速度會很慢,因此您在使用該方法時應當小心。另外一個問題是,由於常量在編譯時間得不到解析,因此每當您拼錯它們時,都會得到執行階段錯誤。在任何情況下,在 .NET 程式集中聲明大多數 Windows API 都可以節省大量工作。例如,SharpZip 樣本程式顯示了與每個檔案的副檔名相關聯的系統表徵圖。為此,代碼調用 com.ms.win32.Shell32 介面中定義的 SHGetFileInfo API 以獲得表徵圖的控制代碼(參見圖 6)。
儘管 com.ms.win32 命名空間非常巨大,但您應當知道它並未包含每個 Windows API 函數和資料結構。例如,com.ms.win32.Shell32 介面的一個顯著疏忽是 SHBrowseForFolder API,它允許我們顯示“Browse for Folder”對話方塊,而無需使用 Microsoft Shell Controls and Automation COM 庫。
還請注意,處理回調有點複雜,這是由於 Java 語言不支援委託。對於每個回調類型,都提供了定義函數原型的抽象類別。您必須從該類派生以實現處理回調的代碼,然後向 API 呼叫傳遞該類的一個執行個體(參見圖 7)。另外一個與 Java 語言有關的較小困難是,按引用傳遞的參數被聲明為數組,但是這隻影響調用這些函數的代碼,而不影響基礎功能。
最後,某些 API 呼叫的轉換非常低劣。一個樣本是 waveOutOpen(定義在 Winmm 類中)。DwCallback 參數在 C++ 中用於傳遞事件控制代碼、視窗控制代碼、線程 ID 或回呼函數,具體取決於 fdwOpen 參數的值。由於 J/Direct 封裝將 dwCallback 參數聲明為 Int32,並且沒有將回調(委託)typecast 到 Int32 的方式,所以必須使用其他通知機制,例如,事件控制代碼、視窗控制代碼或線程 ID。