常用的壓縮工具(如 WinZip 或 ARJ)現(xiàn)在也不起作用,因為我們并不是對整個數(shù)據(jù)庫進行壓縮(那樣光盤上的數(shù)據(jù)將無法檢索),而只是要將數(shù)據(jù)庫中的某些字段的內(nèi)容壓縮后存入庫中——以減少整個數(shù)據(jù)庫占用的空間——然后在使用中動態(tài)解壓將數(shù)據(jù)還原為本來面目。
最理想的辦法當然是數(shù)據(jù)庫系統(tǒng)(DBMS)本身直接支持數(shù)據(jù)壓縮存儲,但令人遺憾的是:常見的DBMS均未提供該功能。
在互聯(lián)網(wǎng)上確實能找到一些有關數(shù)據(jù)壓縮的思想、算法甚至 C語言的部分源代碼,但它們大都過于復雜,或應用范圍有限(如僅對圖像或純數(shù)字數(shù)據(jù)有效),或是在版權方面有太苛刻的要求。
最后我們采用了自行設計的算法。該方法的壓縮率只有 20%至25% ,但它小巧、輕易實現(xiàn),在實際應用中取得了良好的效果。
一、算法概述
壓縮冗余的比特位(bit)是常見的壓縮思想之一。
例如,字符串 CCW-2000 的二進制表示為:
01000011,01000011,01010111,00101101,00110010,00110000,00110000,00110000
其中每個二進制的前導“0”是冗余的,去掉前導“0”后的表示為:
1000011,1000011,1010111,101101,110010,110000,110000,110000
這顯然達到了數(shù)據(jù)壓縮的目的,但同時也帶來一個很大的問題:二進制流僅僅由“0”和“1”組成,并不存在上面為了表述清楚而加入的“,”。 即:由于壓縮后的二進制不再是“定長”的,兩個二進制之間如何“劃界”成了難題。
常見的解決方案有:霍夫曼表(Huffman codes)、LZW的動態(tài)查詢表、Markov的多維長度字典表,以及根據(jù)前值和前長度變換應用規(guī)則的DAKX方法等。但這些方法大都過于繁雜或未公布技術細節(jié)而難以實現(xiàn)。
為了實現(xiàn)起來簡捷,避免用“宰牛刀”,我們設計的算法采用一種折衷的思想:用“縮短”了的“定長”二進制來實現(xiàn)壓縮,同時又避免了“劃界”的難題。
具體思路是:
(1) 所有字符的二進制長度均為 6個 bit而不是一般的 8個 bit。即每個字符節(jié)約 2個 bit。這決定了本方法的理想(最高)壓縮率為 25%。
(2) 由于 6個 bit僅能區(qū)分64種字符,故將內(nèi)碼小于 127的字符分為“常用”和“不常用”兩組。
(3) 第一組由英文字母(大小寫)、0至9的數(shù)字、空格以及一個控制字符
組成;第二組由內(nèi)碼小于 127的其它字符組成。
(4) 每組內(nèi)的字符均重新編碼,以使其“新內(nèi)碼”均小于64。
(5) 第二組的每個字符前均以第一組中的控制字符做前綴。
(6) 內(nèi)碼大于等于 127的字符用普通的 8-bit表示,前綴是兩個連續(xù)的第一組中的控制字符。
(7) 本方法顯然僅適用于西文文本。
(8) 壓縮后的數(shù)據(jù)相當于被加密了,這大大提高了信息安全性。
二、算法實現(xiàn)
我們制作的西歐進出口商數(shù)據(jù)庫中有大量大段的企業(yè)英文描述,故應用上述算法非常成功,如愿以償?shù)貙⒄麄€系統(tǒng)的大小降到了 600兆以下,可以輕松地裝入一張光盤中。
檢索系統(tǒng)用VB編程,數(shù)據(jù)庫用的是 MS-access。
后附程序為清楚起見,將壓縮數(shù)據(jù)庫的功能改為壓縮文本文件的功能。該程序已在機器上測試通過,可作為一個壓縮工具單獨運行(為了同時說明壓縮及解壓縮這兩個函數(shù)的使用,下面的程序會將剛被壓縮的文件再進行解壓縮,解壓后文件與源文件是完全一樣的)。
附完整的VB源代碼:
Option EXPlicit
Dim comped$, comping As String * 1, comping_p%, bit_p&
Dim ebitmask(1 To 8) As Integer ' 存儲掩碼的數(shù)組
Sub Command1_Click ()
Dim ibuf$, obuf1$, obuf2$
MousePointer = 11
' 設置掩碼
ebitmask(1) = 128: ebitmask(2) = 64: ebitmask(3) = 32: ebitmask(4) = 16
ebitmask(5) = 8: ebitmask(6) = 4: ebitmask(7) = 2: ebitmask(8) = 1
Open "d:/temp/comPRess/theory.txt" For Input As #1 ' 壓縮前的源文件
Open "d:/temp/compress/theory.6BT" For Output As #2 ' 壓縮后的文件
Open "d:/temp/compress/theory_2.txt" For Output As #3 ' 解壓后的文件
Do While Not EOF(1)
Line Input #1, ibuf
obuf1 = DoCompress(ibuf)
Print #2, obuf1
obuf2 = UnDoCompress(obuf1)
Print #3, obuf2
Loop
Close #1, #2, #3
MousePointer = 0
End Sub
Function DoCompress (in_buf$) As String ' 對輸入的字符串進行壓縮
Dim i&, buf_len&, c As String * 1
comped = "": comping = Chr(0): comping_p = 0
buf_len = Len(in_buf)
If buf_len > 0 Then
For i = 1 To buf_len
c = Mid(in_buf, i, 1)
Select Case c
Case " ", "A" To "Z", "a" To "z"
putbits 0, c ' 第一組中的字符
Case "!
" To "/", ":" To "@", "[" To "`", "{" To "~", Chr(1) To Chr(31)
putbits 1, c ' 第二組中的字符
Case Else
putbits 2, c ' 其它字符
End Select
Next i
putbits 3, Chr(0)
End If
DoCompress = comped
End Function
Sub putbits (flag%, cc$) ' 壓縮冗余的比特位(bits)
Dim i%, c As String * 1
c = cc
Select Case flag
Case 0 '對第一組中的字符內(nèi)碼進行重新定位
Select Case c
Case " "
c = Chr(1)
Case "0" To "9"
c = Chr(Asc(c) - 46)
Case "A" To "Z"
c = Chr(Asc(c) - 53)
Case "a" To "z"
c = Chr(Asc(c) - 59)
End Select
Case 1 '對第二組中的字符內(nèi)碼進行重新定位
Select Case c
Case "!" To "/"
c = Chr(Asc(c) - 32)
Case ":" To "@"
c = Chr(Asc(c) - 42)
Case "[" To "`"
c = Chr(Asc(c) - 68)
Case "{" To "~"
c = Chr(Asc(c) - 94)
Case Chr(1) To Chr(31)
c = Chr(Asc(c) + 32)
End Select
For i = 1 To 6
putbit 0
Next i
Case 2
For i = 1 To 12
putbit 0
Next i
For i = 1 To 8
If (Asc(c) And ebitmask(i)) <> 0 Then
putbit 1
Else
putbit 0
End If
Next i
Case 3
For i = comping_p + 1 To 9
putbit 0
Next i
End Select
If flag < 2 Then
For i = 1 To 6
If (Asc(c) And ebitmask(i + 2)) <> 0 Then
putbit 1
Else
putbit 0
End If
Next i
End If
End Sub
Sub putbit (bit%) ' 設置比特位(bit)
comping_p = comping_p + 1
If comping_p > 8 Then
comped = comped + comping
comping = Chr(0)
comping_p = 1
End If
If bit = 1 Then
comping = Chr(Asc(comping) Or ebitmask(comping_p))
End If
End Sub
Function UnDoCompress (in_buf$) As String ' 對輸入的字符串進行解壓縮
Dim bits_buf%, out_buf$, c As String * 1, comped_len&
comped = in_buf: bit_p = 1: comped_len = Len(comped) * 8
Do While bit_p <= comped_len
If comped_len - bit_p < 5 Then
Exit Do
End If
bits_buf = getbits(6)
If bits_buf <> 0 Then ' 根據(jù)控制字符判定字符的組別
Select Case bits_buf
Case 1
c = " "
Case 2 To 11 ' "0" To "9"
c = Chr(bits_buf + 46)
Case 12 To 37 ' "A" To "Z"
c = Chr(bits_buf + 53)
Case 38 To 63 ' "a" To "z"
c = Chr(bits_buf + 59)
End Select
out_buf = out_buf + c
Else
If bit_p > comped_len Then
Exit Do
End If
bits_buf = getbits(6)
If bits_buf <> 0 Then
Select Case bits_buf
Case 1 To 15 ' "!" To "/"
c = Chr(bits_buf + 32)
Case 16 To 22 ' ":" To "@"
c = Chr(bits_buf + 42)
Case 23 To 28 ' "[" To "`"
c = Chr(bits_buf + 68)
Case 29 To 32 ' "{" To "~"
c = Chr(bits_buf + 94)
Case 33 To 63 ' Chr(1) To Chr(31)
c = Chr(bits_buf - 32)
End Select
out_buf = out_buf + c
Else
bits_buf = getbits(8)
out_buf = out_buf + Chr(bits_buf)
End If
End If
Loop
UnDoCompress = out_buf
End Function
Function getbits (numb As Integer) As Integer ' 獲取比特位(bit)
Dim byte_p&, bit_o%, byte1 As String * 1, i%, j%, k%, m%
byte_p = (bit_p / 8) + 1: bit_o = bit_p Mod 8
byte1 = Mid(comped, byte_p, 1): j = 0: k = 0
For i = bit_o To 8
k = k + 1
If k > numb Then
Exit For
End If
If (Asc(byte1) And ebitmask(i)) <
> 0 Then
j = j Or e