通過 C# 使用 J# 類庫中的 Zip 類壓縮檔和資料

來源:互聯網
上載者:User
資料|壓縮 本文假設您熟悉 C# 和 Windows 表單

下載本文的代碼: ZipCompression.exe (150KB)

摘要

在隱藏檔或者通過網路傳送檔案時,使用 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 類的原因。

正像您將在本文稍後看到的那樣,J# 運行庫公開的某些類實際上並不符合 CLS 規範,但是您仍然可以從其他語言中訪問大多數 J# 類,以便使用 .NET Framework 未實現的特定功能。由於 J# 實現了 JDK 版本 1.1.4,因此絲毫不會令人感到意外的是,開發人員可以通過 J# 運行庫訪問 java.util.zip 命名空間。在本文的下一部分中,我將介紹一個用 C# 編寫的應用程式,它使用 java.util.zip 類壓縮和解壓縮 Zip 檔案,以便在本地節省空間的以及在網路中節省頻寬。

本文中的所有範例程式碼都是用 Microsoft Visual Studio 2002 和 J# 運行庫版本 1.0(參見位於本文頂部的連結)開發的。

返回頁首
SharpZip


我用 C# 編寫了本文隨附的應用程式範例之一 SharpZip。它是一個用於處理 Zip 檔案的簡化工具 + 生產力,通過它可以建立 Zip 檔案,或者開啟現有的 Zip 檔案以解壓縮、附加和刪除檔案(參見圖 1)。



圖 1 SharpZip 應用程式


在查看代碼之前,您需要確保在系統中正確安裝了 J# 運行庫。無需安裝完整的 Visual J# .NET 產品。您可以只下載並安裝 J# 1.0 Redistributable Package,它可以從 http://msdn.microsoft.com/vjsharp/downloads/howtoget.asp 獲得。

Java.util.zip 命名空間在 vjslib.dll 程式集中實現。該程式集位於 C:\WINNT\Microsoft Visual JSharp .NET\Framework\v1.0.4205\ 目錄中(您需要將 WINNT 替換為實際的 Windows 目錄)。

在項目中包含對 vjslib.dll 的引用時,可以開始從代碼中使用 J# 命名空間並且用物件瀏覽器瀏覽 JDK 命名空間(參見圖 2)。重要的類包括 java.util.zip.ZipFile、java.util.zip.ZipEntry 和 java.util.zip.ZipOutputStream。這些類顯示在圖 3 中,通過它們可以在檔案層級操縱 Zip 檔案。



圖 2 物件瀏覽器中的命名空間


在使用本文中概述的方法時,方法名稱在您看來可能是陌生的,這是因為 Java 用於標識符(除類和介面外)的命名規範與在 C# 中使用的命名規範有所不同。在 Java 中,命名空間和方法名稱是使用低級大小寫混合編寫的,其中第一個字母小寫,其餘單詞為首字母大寫,如“nextElement”所示。但是,我肯定您會掌握這種方法的。

返回頁首
枚舉 Zip 條目


Java.util.zip.ZipFile 類的 entries 方法返回一個實現 java.util.Enumeration 介面的對象。然後,應用程式遍曆枚舉,以檢索表示 Zip 檔案中的各個條目的 ZipEntry 執行個體。ZipEntry 類將公開所有需要的資訊,例如,檔案名稱、壓縮方法、時間戳記、原始大小和壓縮大小等等(參見圖 4)。

請注意,儘管 java.util.Enumeration 介面類似於 System.Collections.IEnumerator 介面,但 Java 列舉程式在您通過調用 nextElement 檢索當前對象時前進至下一個元素,而 .NET 列舉程式當您在 MoveNext 調用中檢查更多元素的可用性時前進。另一個重要差異是 Enumeration 介面不提供用於重新啟動遍曆的方法。

.NET 列舉程式的一個優點是您可以多次訪問當前元素。另一方面,Java 列舉程式使您可以多次檢查完成情況,但是這在大多數情況下不是非常有用。Java 和 .NET 列舉程式都經過了良好的設計,能夠防止您在枚舉迴圈內部忘記前進至下一個元素。

我決定編寫一個用於封裝 Java 列舉程式的類,以便我可以將 C# foreach 語句與它們一起使用。我將該類命名為 EnumerationAdapter。我通過再次調用能夠返回 Java 列舉程式的方法來類比 Reset 方法。為此,封裝類建構函式採用 java.util.Enumeration 介面的委託作為參數,而不是 java.util.Enumeration 介面本身作為參數。

返回頁首
解壓縮 Zip 檔案


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 忽略了目錄條目,並且從檔案名稱中提取必要的路徑資訊。

要將單個檔案儲存到磁碟,只需將與感興趣的條目相對應的 InputStream 的內容寫入檔案。這一次我決定不將自訂 System.IO.Stream 類封裝為 Java 流,因為 java.io 命名空間對於流具有相當好的支援。特別地,java.io.FileOutputStream 使您可以建立檔案以便向其複製所需的條目。

圖 5 中的 CopyStream Helper 函數將 java.io.InputStream 對象的內容複寫到 java.io.OutputStream 對象。該 Helper 函數還被 SharpZip 應用程式的其他部分使用。可是,您應當注意,該樣本在改寫輸出檔案之前不會檢查它們是否已經存在。您可能希望通過詢問是否應當改寫該檔案來提示使用者。

還要注意,沒有針對密碼保護檔案的支援。您可以使用 System.Security.Cryptography 命名空間中的類建立自己的加密機制。如果您這樣做,則請注意,產生的檔案將不與標準 Zip 工具 + 生產力(例如,WinZip)相容。

返回頁首
建立和修改 Zip 檔案


Java.util.zip.ZipOutputStream 類使您可以壓縮資料並且將結果寫入基礎 java.io.OutputStream 對象。SharpZip 應用程式適合於處理檔案,因此它將壓縮資料寫入一個新的 java.io.FileOutputStream 對象,但是您可以容易地從 java.io.OutputStream 派生自己的類,或者使用標準類之一將壓縮資料直接寫入網路或其他儲存介質。

CreateEmptyZipFile Helper 函數建立一個 Zip 檔案並且立即關閉它。結果得到一個不含任何條目的空 Zip 檔案。追加或刪除項就沒有那麼簡單了,因為 java.util.zip 包不提供對 Zip 檔案的隨機訪問。對於刪除檔案,應當將想要保留的條目複製到新的 Zip 檔案。對於添加檔案,應當將所有條目複製到新的 Zip 檔案,然後追加新條目。複製條目涉及到按照我已經描述的方式從源檔案中解壓縮條目,然後將其重新壓縮到目標檔案。

為想要添加的每個檔案建立一個新的 ZipEntry 執行個體,並且對該條目調用 setMethod 以設定要使用的壓縮方法。受支援的方法是 ZipEntry.DEFLATED(它使用壓縮演算法壓縮資料)和 ZipEntry.STORED(它儲存資料但不應用任何壓縮)。然後調用 ZipOutputStream.putNextEntry,同時傳入新條目,然後通過調用 ZipOutputStream 對象上的寫入方法寫入它的資料。在完成當前條目的處理時,調用 ZipOutputStream.closeEntry 並繼續處理下一個條目。

圖 5 中的 UpdateZipFile 函數通過為每個條目調用委託實現了更新和刪除,以便您可以選擇應當將哪些條目複製到臨時檔案。最後,新條目被添加到 Zip 檔案。

返回頁首
低層級 Zip 壓縮


使用 java.util.zip 類,不僅可以壓縮檔,還可以壓縮應用程式資料。為了說明這一點,我建立了一對函數,以便使用 java.util.zip.Deflater 和 java.util.zip.Inflater 類壓縮和解壓縮字串。

壓縮函數將建立一個 java.util.zip.Deflater 類的執行個體。建構函式中的一個參數定義所需的壓縮層級。接下來,我調用 Deflater.setInput 類,同時將要壓縮的資料作為帶符號的位元組 (sbyte) 數組進行傳遞,然後調用 Deflater.finish。

請注意,與 C# 相反,Java 中的 byte 資料類型是帶符號的 — Java 中沒有無符號 byte 資料類型。這就是 J# 運行庫的所有處理緩衝區的方法都採用 sbyte 數組作為參數的原因。

幸運的是,com.ms.vjsharp.struct 命名空間包含 JavaStructMarshalHelper 類,該類除了具有其他功能以外,還能夠協助您執行數群組轉換。CompressString 函數調用 convertToByteArray 方法,以便將字串轉換為帶符號的位元組數組。為了獲得實際的壓縮位,我只是不停地調用 Deflater.deflate,直到 Deflater.finished 返回真以表示已經消耗盡所有輸入資料。我在壓縮迴圈內部使用 java.io.ByteArrayOutputStream 的執行個體收集產生的資料。作為一般規則,在 C# 中處理 Java 類型時,最好使用 JDK 類。它是避免在 sbyte 和 byte 之間反覆轉換數組的最佳方式。

用於解壓縮字串的代碼看起來非常類似於用於壓縮的代碼。這一次,建立一個 java.util.zip.Inflater 類的執行個體並調用 setInput 方法,同時傳入壓縮資料。解壓縮迴圈不斷地調用 Inflater.inflate,直到 Inflate.finished 變為真,表示所有輸入資料都已經被解壓縮。最後,調用 JavaStructMarshalHelper.convertToString 以便將無符號位元組數群組轉換為要由該函數返回的字串。

CsZipLL 應用程式範例(LL 代表低層級)建立一個長字串並且將其壓縮至大約一半大小。您可以使用這些函數完成某些工作,例如,編寫 SOAP 延伸模組以減少 Web 服務所需的網路頻寬。

返回頁首
J# 的其他迷人的功能


儘管本文重點介紹如何處理 Zip 檔案,但該原則也可以應用於 J# 運行庫提供了無法從 .NET Framework 標準程式集中獲得的功能的其他領域。

由於 J# 為開發人員提供了將他們的 Visual J++ 項目遷移到 .NET Framework 的途徑,因此 J# 還實現了很多特定於 Visual J++ 的功能,例如 J/Direct?。J/Direct 技術使 Java 語言程式可以調用本機 Windows 代碼。像 Visual J++ 中一樣,J# 中的 com.ms.win32 命名空間提供了對大多數 Windows API 函數、資料類型和常量的訪問。

User32、Kernel32 和 Gdi32 類包含 Win32?API 函數的核心。這些常量在一些名為 winx(其中,x 是常量的首字母)的介面中被定義為靜態欄位。例如,ShowWindow API 的 SW_SHOW 標誌可以在 com.ms.win32.wins 介面中找到。

為了使介面符合 CLS 規範,它不得包含欄位,而 com.ms.win32.winx 介面無法通過該測試。因為 C# 不允許在介面中使用欄位,所以 IntelliSense 和 C# 編譯器都看不到這些常量,但是您仍然可以使用反射訪問這些欄位,如下所示:

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)。

請注意,當您從控制代碼建立 System.Drawing.Icon 對象時,新 Icon 將不擁有該控制代碼。這意味著,您必須通過調用 DestroyIcon API 釋放關聯的資源。由於我不希望在 Icon 對象的整個生存期記憶體儲表徵圖控制代碼,因此我選擇通過使用其控制代碼上的複製建構函式建立產生 object.Icon 的副本。

儘管 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。

在核心 J# 包中,還有其他一些有趣的東西。例如,java.math.BigDecimal 和 java.math.BigIntegers 類使您可以操縱任意大的數字,這在您編寫應用程式以處理密碼編譯演算法或科學計算時可能非常有用。

CsMath 樣本項目顯示了如何使用 java.math.BigDecimal,通過 Machin 的公式來計算在小數點後帶有任意個數位 Pi。為了使代碼更易讀,我在自己的 BigDecimal 類中封裝了 java.math.BigDecimal,並且定義了最常用的運算子。

返回頁首
應用程式部署


使用該技術的應用程式要求在目標電腦上安裝 J# 運行庫和 .NET Framework。就像 .NET Framework 一樣,Microsoft 提供了一個可以與應用程式安裝程式一起部署的可重新分發的包。

Microsoft 已經表示將繼續為案頭作業系統支援 J#。但是,當前 J# 中沒有對 .NET Compact Framework 的支援,因此您無法將本文介紹的技術應用於面向智慧型裝置的應用程式。將程式集複製到本地項目目錄的操作將無效,因為 J# 運行庫程式集極度依賴於本機調用。但是,您可以為使用移動 Web 控制項的 Web 應用程式充分利用 J# 運行庫。

返回頁首
小結


J# 運行庫包含很多可以從 .NET Framework 中的其他語言使用的有用的類。其中一些類使您可以處理 Zip 檔案、執行高精度數學計算或者調用 Windows API。儘管可以通過使用第三方庫獲得該功能的大部分,但 J# 運行庫受到 Microsoft 的充分支援,並且是免費的!

相關文章,請參閱:
Java 911: Parlez-vous J/Direct?
有關背景資訊,請參閱:
http://msdn.microsoft.com/vjsharp/
What is the Common Language Specification?

Ianier Munoz 是 Dokumenta 的一名軟體架構師和分析師,該公司是一家總部位於盧森堡的諮詢公司。他還創作了 Chronotron 和其他一些流行軟體。您可以通過 http://www.chronotron.com 與他聯絡。

轉到原英文頁面





相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。