VBA解压缩ZIP文件10——解压-动态Huffman

时间:2022-07-22
本文章向大家介绍VBA解压缩ZIP文件10——解压-动态Huffman,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

使用动态Huffman压缩的数据块,在数据块的开头仍然是3个bit的Header,第2个bit是0、第3个bit是1,因为读取过程是先读取低位,再读取高位,所以结果应该是二进制10。

01

解析h3 Huffman树

接下来的压缩数据块的bit流分别是:

  • HLIT:5比特,记录literal/length码树中码长序列(CL1)个数的一个变量。
  • HDIST:5比特,记录distance码树中码长序列(CL2)个数的一个变量。
  • HCLEN:4比特,记录Huffman码表3中码长序列(CCL)个数的一个变量。
  • 接下来是每3个比特编码一个CCL,一共HCLEN+4个,用以构造Huffman码表3

读取到这里的时候,CCL数组的数据就读取到了,然后使用CCL数组去创建h3(编码SQ1和SQ2)Huffman树。

02

解析h1、h2 Huffman树

得到了h3后,继续读取压缩数据块后面的bit流,并使用h3进行解码,得到SQ1,使用行程编码进行解析,得到CL1,然后创建h1(编码literal和length)Huffman树。

继续读取压缩数据块后面的bit流,并使用h3进行解码,得到SQ2,使用行程编码进行解析,得到CL2,然后创建h2(编码distance)Huffman树。

解析h3、h1、h2 Huffman树的代码实现:

'解析h1和h2
'参数h1和h2是用来返回Huffman树的
Private Function parseH1AndH2(h1 As CHuffmanTree, h2 As CHuffmanTree, ByRef cpByte() As Byte, ByRef bitIndex As Long) As Long
    Dim bValue As Long
    Dim i As Long

    'HLIT:5比特,记录literal/length码树中码长序列(CL1)个数的一个变量。
    '后面CL1个数等于HLIT+257(因为至少有0-255总共256个literal,还有一个256表示解码结束,但length的个数不定)。
    bValue = GetBits(cpByte, bitIndex, 5)
    bitIndex = bitIndex + 5
    Dim iCL1 As Long
    iCL1 = bValue + 257
    
    'HDIST:5比特,记录distance码树中码长序列(CL2)个数的一个变量。
    '后面CL2个数等于HDIST+1。哪怕没有1个重复字符串,distance都为0也是一个CL。
    bValue = GetBits(cpByte, bitIndex, 5)
    bitIndex = bitIndex + 5
    Dim iCL2 As Long
    iCL2 = bValue + 1

    'HCLEN:4比特,记录Huffman码表3中码长序列(CCL)个数的一个变量。
    '后面CCL个数等于HCLEN+4。PK认为CCL个数不会低于4个,即使对于整个文件只有1个字符的情况。
    bValue = GetBits(cpByte, bitIndex, 4)
    bitIndex = bitIndex + 4
    Dim iCCL As Long
    iCCL = bValue + 4
    
    '接下来是3比特编码的CCL,一共HCLEN+4个,用以构造Huffman码表3
    '针对SQ1、SQ2,PK用了第三个Huffman码表来对这两个序列进行编码。
    '通过统计各个整数(0-18范围内)的出现次数,按照相同的思路,
    '对SQ1和SQ2进行了Huffman编码,得到的码流记为SQ1 bits和SQ2 bits。同时,这里又需要记录第三个码表,称为Huffman码表3。
    '同理,这个码表也用相同的方法记录,也等效为一个码长序列,称为CCL,因为至多有0-18个,PK认为树的深度至多为7,于是CCL的范围是0-7。
    Dim CCL() As Long
    '标准的CCL长度为19
    ReDim CCL(18) As Long
    
    For i = 0 To iCCL - 1
        bValue = GetBits(cpByte, bitIndex, 3)
        bitIndex = bitIndex + 3
        '当得到了CCL序列后,即000代表0,111代表7。
        '这个序列如果全部记录,那就需要19*3=57个比特,PK认为CL序列里面CL范围为0-15,
        '特殊的几个值是16、17、18,如果把CCL序列位置置换一下,把16、17、18这些放前面,
        '那么这个CCL序列就很可能最后面跟着一串0(因为CL=14,15这些很可能没有),所以最后还引入了一个置换
        CCL(CCLSwapValue(i)) = bValue
    Next
    
    Dim htCCL As CHuffmanTree
    Set htCCL = CreateHuffman(CCL)
'    htCCL.PrintOut
    
    Dim CL1() As Long
    ReDim CL1(iCL1 - 1) As Long
    parseByHuffman3 htCCL, cpByte, bitIndex, CL1
    '得到literal/length码表(Huffman码表1),构造Huffman树,解析LIT比特流
    Set h1 = CreateHuffman(CL1)
    
    Dim CL2() As Long
    ReDim CL2(iCL2 - 1) As Long
    parseByHuffman3 htCCL, cpByte, bitIndex, CL2
    '得到Distance码表(Huffman码表2),构造Huffman树,解析DIST比特流
    Set h2 = CreateHuffman(CL2)
    
    Set htCCL = Nothing
End Function

解压数据

h1、h2创建之后,继续读取压缩数据块后面的bit流,使用h1、h2进行解码,解压出原始数据。

'ZIP里的压缩算法称为Deflate算法
'对应的解压缩算法称为Inflate
Private Function InflateByHuffman(h1 As CHuffmanTree, h2 As CHuffmanTree, ByRef cpByte() As Byte, ByRef uncpByte() As Byte, ByRef bitIndex As Long, ByRef pUncp As Long) As Long
    Dim ilen As Long
    Dim iDistance As Long
    Dim i As Long
    
    Dim bValue As Long
    '按h1解码一个数字
    bValue = h1.GetLeafKey(cpByte, bitIndex)
    Do Until bValue = 256
    
        If bValue < 256 Then
'            Debug.Print pUncp, VBA.Chr(bValue)
            
            uncpByte(pUncp) = bValue
            pUncp = pUncp + 1
        Else
            ilen = bValue - 257
            ilen = ExtraValue(ilen, LengthCode, cpByte, bitIndex)
            '按h2解码distance
            iDistance = h2.GetLeafKey(cpByte, bitIndex)
            iDistance = ExtraValue(iDistance, DistanceCode, cpByte, bitIndex)
            
            '根据长度和距离复制数据
            '一个复制的参考串可能指向前一个块,如:向后的distnace可能
            '穿过一个或几个块边界,但是distance不可能越过输出串的开始
            For i = 1 To ilen
                uncpByte(pUncp) = uncpByte(pUncp - iDistance)
                
                pUncp = pUncp + 1
            Next
        End If
        
        bValue = h1.GetLeafKey(cpByte, bitIndex)
    Loop
    
    Set h1 = Nothing
    Set h2 = Nothing
End Function