用.NET 2.0壓縮/解壓功能處理大型資料

NO IMAGE

摘要 如果你的應用程式從未使用過壓縮,那麼你很幸運。而對於另一部分使用壓縮的開發人員來說,好訊息是,.NET 2.0如今提供了兩個類來處理壓縮和解壓問題。本文正是想討論何時以及如何使用這些有用的工具。

引言

.NET框架2.0中的一個新名稱空間是System.IO.Compression。這個新名稱空間提供了兩個資料壓縮類:DeflateStream和GZipStream。這兩個壓縮類都支援無失真壓縮和解壓,其設計目的是為了處理流式資料的壓縮和解壓問題。

壓縮是減少資料大小的有效辦法。例如,如果你有巨大量的資料儲存在你的SQL資料庫中,那麼如果你在把這些資料儲存到一個表之前壓縮一下,你就可以節省大量的磁碟空間。而且,既然現在你把更小塊的資料儲存到你的資料庫中,花費在磁碟I/O方面的操作將會大大減少。壓縮的缺點是,它要求你的機器進行另外的處理(因此需要另外的處理時間),並且,在你決定把壓縮應用於你的程式之前,你需要計算這一部分時間。

壓縮在你需要在網上傳送資料的情況中是極其有用的,特別是對於非常慢且代價昂貴的網路,例如GPRS連線。在這種情況中,使用壓縮能夠極大地縮小資料尺寸並且減少整個通訊耗費。Web服務是另一個領域-此時,使用壓縮能提供巨大的優點,因為XML資料能被高度壓縮。

但是一旦你認為程式的效能代價值得使用壓縮,那麼你將需要深入地理解.NET 2.0的兩個新的壓縮類,而這正是我想在本文中所闡述的。

建立示例應用程式

在本文中,我將構建一個示例應用程式來展示壓縮的使用。該應用程式允許你壓縮檔案,包括普通文字檔案。然後,你能夠把該示例中的程式碼重用於你自己的應用程式中。

首先,使用Visual Studio 2005建立一個新的Windows應用程式並且使用下列控制元件來填充預設的表單(見圖1):

按此在新視窗瀏覽圖片

圖1.填充表單:使用所有顯示的控制元件填充預設的Form1。

· GroupBox控制元件

· RadioButton控制元件

· TextBox控制元件

· Button控制元件

· Label控制元件

切換到Form1的code-behind並且匯入下列名稱空間:

Imports System.IO

Imports System.IO.Compression

在你開始使用壓縮類前,理解其工作原理是非常重要的。這些壓縮類從一個位元組陣列中讀取資料,壓縮它並且把結果儲存到一個流物件中。對於解壓來說,解壓儲存到一個流物件中的資料,然後把它儲存到另一個流物件中。

首先,定義Compress()函式,它有兩個引數:algo和data。第一個引數指定使用哪種演算法(GZip或Deflate);第二個引數是一個包含要壓縮的資料的位元組陣列。一個記憶體流物件將被用來儲存壓縮資料。一旦壓縮完成,你需要計算壓縮比,這是用壓縮的資料的大小除以解壓資料的大小計算的。

然後,儲存在記憶體流中的壓縮的資料被複制到另一個位元組陣列中並且被返回到呼叫函式。另外,你還要使用一個StopWatch物件來跟蹤該壓縮演算法使用了多少時間。Compress()函式定義如下:

Public Function Compress(ByVal algo As String, ByVal data() As Byte) As Byte()

Try

Dim sw As New Stopwatch

‘—ms用於儲存壓縮的資料—

Dim ms As New MemoryStream()

Dim zipStream As Stream = Nothing

‘—開始秒錶計時—

sw.Start()

If algo = “Gzip” Then

zipStream = New GZipStream(ms, CompressionMode.Compress, True)

ElseIf algo = “Deflate” Then

zipStream = New DeflateStream(ms, CompressionMode.Compress, True)

End If

‘—使用儲存在資料中的資訊進行壓縮—

zipStream.Write(data, 0, data.Length)

zipStream.Close()

‘—停止秒錶—

sw.Stop()

‘—計算壓縮比—

Dim ratio As Single = Math.Round((ms.Length / data.Length) * 100, 2)

Dim msg As String = “Original size: ” & data.Length & _

“, Compressed size: ” & ms.Length & _

“, 壓縮比: ” & ratio & “%” & _

“, Time spent: ” & sw.ElapsedMilliseconds & “ms”

lblMessage.Text = msg

ms.Position = 0

‘—用來儲存壓縮了的資料(位元組陣列)—

Dim c_data(ms.Length – 1) As Byte

‘—把記憶體流的內容讀取到位元組陣列—

ms.Read(c_data, 0, ms.Length)

Return c_data

Catch ex As Exception

MsgBox(ex.ToString)

Return Nothing

End Try

End Function

這個Decompress()函式將解壓由Compress()函式壓縮的資料。第一個引數指定要使用的演算法。包含壓縮的資料的位元組陣列被作為第二個引數傳遞,然後它被複制到一個記憶體流物件中。然後,這些壓縮類將解壓儲存在記憶體流中的資料,然後把解壓的資料儲存到另一個流物件中。為了獲得解壓的資料,你需要讀取來自流物件的資料。這是通過使用RetrieveBytesFromStream()函式來實現的(將在後面解釋)。

Decompress()函式的定義如下所示:

Public Function Decompress(ByVal algo As String, ByVal data() As Byte) As Byte()

Try

Dim sw As New Stopwatch

‘—複製資料(壓縮的)到ms—

Dim ms As New MemoryStream(data)

Dim zipStream As Stream = Nothing

‘—開始秒錶—

sw.Start()

‘—使用儲存在ms中的資料解壓—

If algo = “Gzip” Then

zipStream = New GZipStream(ms, CompressionMode.Decompress)

ElseIf algo = “Deflate” Then

zipStream = New DeflateStream(ms, CompressionMode.Decompress, True)

End If

‘—用來儲存解壓的資料—

Dim dc_data() As Byte

‘—解壓的資料儲存於zipStream中;

‘把它們提取到一個位元組陣列中—

dc_data = RetrieveBytesFromStream(zipStream, data.Length)

‘—停止秒錶—

sw.Stop()

lblMessage.Text = “Decompression completed. Time spent: ” & _

sw.ElapsedMilliseconds & “ms” & _

“, Original size: ” & dc_data.Length

Return dc_data

Catch ex As Exception

MsgBox(ex.ToString)

Return Nothing

End Try

End Function

這個RetrieveBytesFromStream()函式使用了兩個引數:一個流物件,一個整數,並返回一個包含解壓的資料的位元組陣列。這個整數引數用於決定每次把多少個位元組從該流物件中讀取到位元組陣列中。這是必要的,因為當資料被解壓時,你不知道存在於流物件中的解壓資料的大小。因此,有必要動態地把位元組陣列擴充套件成塊以便儲存在執行時刻期間解壓縮的資料中。當你不斷地擴充套件位元組陣列時,塊太大會浪費記憶體,而塊太小則會失去珍貴的時間。因此,可以由呼叫例程來決定要讀取的最佳塊大小。

RetrieveBytesFromStream()函式的定義如下:

Public Function RetrieveBytesFromStream( _

ByVal stream As Stream, ByVal bytesblock As Integer) As Byte()

‘—從一個流物件中檢索位元組—

Dim data() As Byte

Dim totalCount As Integer = 0

Try

While True

‘—逐漸地增加資料位元組陣列-的大小–

ReDim Preserve data(totalCount bytesblock)

Dim bytesRead As Integer = stream.Read(data, totalCount, bytesblock)

If bytesRead = 0 Then

Exit While

End If

totalCount = bytesRead

End While

‘—確保位元組陣列正確包含提取的位元組數—

ReDim Preserve data(totalCount – 1)

Return data

Catch ex As Exception

MsgBox(ex.ToString)

Return Nothing

End Try

End Function

注意,在Decompress()函式中,你呼叫了RetrieveBytesFromStream()函式,如下所示:

dc_data = RetrieveBytesFromStream(zipStream, data.Length)

塊大小是指壓縮的資料的大小(data.length)。在大多數情況中,解壓縮的資料要比壓縮的資料大幾倍(由壓縮比所顯示),因此,在執行時刻期間你將至多動態地擴充套件位元組陣列幾倍。作為一個例子,假定壓縮比是百分之20而壓縮的資料的大小為2MB,那麼,在這種情況中,解壓的資料將是10MB。因此,該位元組陣列將被動態地擴充套件5倍。理想情況下,在執行時刻期間該位元組陣列不應該被擴充套件太頻繁,因為這將會嚴重地減慢應用程式執行速度。但是使用壓縮的資料的大小作為塊大小確是一種好的辦法。