nginx中的哈夫曼编码算法-编码
nginx中的哈夫曼编码算法-解码[上]
nginx中的哈夫曼编码算法-解码[中]
nginx中的哈夫曼编码算法-解码[下]
1. 引言
在[《nginx中的哈夫曼编码算法》](https://blog.csdn.net/bluestn/article/details/138095355)中,我们介绍了nginx采用查表的方法来实现的哈夫曼编码对http2 hpack进行压缩的功能,其编码的实现原理还是比较简单的。然而,上山容易下山难,nginx中实现的快速哈夫曼解码算法在理解上相对于编码算法有一些难度的。今天我们来聊一聊nginx是如何来实现快速哈夫曼解码的。
为什么要增加快速
这个形容词呢?因为在学习哈夫曼原理的时候,书本上介绍的是采用构建哈夫曼树的方式,通过一边读取输入流中的比特,一边在哈夫曼树中不断游走的方式来实现的解码方式,虽然这种方式比较容易理解,但是其解码效率是不那么理想的。而nginx采用构建状态转移矩阵,在解码时不是每次读取一个比特,而是每次一次性读取输入流中的4个比特,通过跟随状态转移矩阵不断更新状态,来实现快速解码,解码效率得到大幅提升。
而且nginx在状态转移矩阵也进行了优化,能够在解码的过程中,对于多读取的比特不进行回退就能够正确解码,相比于需要回退的算法能够在性能上表现更加优秀。
本文分三部分进行讲解,首先介绍nginx实现的哈夫曼解码算法中的状态转移矩阵的构造及利用状态转移矩阵如何进行解码的原理;接着我们结合nginx的源码来详细分析nginx的解码源码的实现原理;最后,介绍快速哈夫曼解码算法的最核心的内容,就是如何来构造状态转移矩阵。
在深入阅读本文之前,大家也可以参考我之前发表的《采用状态转移矩阵方式的快速哈夫曼解码算法》,预先了解用状态转移表进行哈夫曼解码的原理。
2. 哈夫曼解码状态转移矩阵
在nginx的哈夫曼解码的相关代码里面,首先它定义了一个状态转移矩阵,如下:
typedef struct {
u_char next;
u_char emit;
u_char sym;
u_char ending;
} ngx_http_huff_decode_code_t;
static ngx_http_huff_decode_code_t ngx_http_huff_decode_codes[256][16] = {
......
}
先了解释一下ngx_http_huff_decode_code_t结构体的每个字段的含义。
- next: 下一个状态的状态id
- emit: 是否可以输出sym(被解码出来的字符)
- sym: 如果 emit=1,那么sym表示被解码出来的字符
- ending: 本状态是否可以进入解码完成状态
emsp;在看看ngx_http_huff_decode_codes,它是一个二维数组,第一维有256个记录,表示256个状态,第二纬有16个元素,表示在当前的状态下面,后面读取到新的4个bit(总共16种排序方式,包括:0000,0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111)后,如何进行处理,包括是输出解码得到的字符,还是只是进行状态转移,疑惑是既要输出解码得到的字符又要进行状态转移。
所以这里第二维中的每个元素可以看成是状态转移图中的转移弧,并且每个状态都有16条状态转移弧。
在这个状态转移矩阵中,ngx_http_huff_decode_codes的第零条记录被规定为起始状态,解码的时候从状态零开始,不断重复读进4个bit,然后根据当前状态下对应的转移弧来进行处理,直到解码出所有的字符。
举个例子,譬如,0a0a9bc\xf8
的哈夫曼编码为 00 c0 37 e3 27 ff ff eb
,按照nginx定义的状态转移矩阵,人工进行解码,将过程总结成一个表格,如下:
当前状态 | 输入 (二进制) | 下一状态 | 输出字符 | 结束标记 |
---|---|---|---|---|
0 | 0000 | 4 | - | 0 |
4 | 0000 | 3 | 0x30(0) | 0 |
3 | 1100 | 2 | 0x61(a) | 0 |
2 | 0000 | 1 | 0x30(0) | 0 |
0 | 0011 | 0 | 0x61(a) | 0 |
0 | 0111 | 19 | - | 0 |
19 | 1110 | 23 | 0x39(9) | 0 |
23 | 0011 | 0 | 0x62(b) | 0 |
0 | 0010 | 7 | - | 0 |
7 | 0111 | 56 | 0x63© | 1 |
56 | 1111 | 74 | - | 1 |
74 | 1111 | 88 | - | 1 |
88 | 1111 | 95 | - | 1 |
95 | 1111 | 169 | - | 1 |
169 | 1110 | 208 | - | 1 |
208 | 1011 | 0 | 0xf8 | 1 |
需要特别说明的是上述表格的”结束标记“,结束标记表示当前状态下遇到特定的4个比特的输入后,转移到新的状态时,这个新的状态可能是结束态。
如果输入流所有的比特都读取完毕了以后,但是当前的解码状态不是”结束状态“,那么认为当前的哈夫曼码流是有问题的,所以解码器根据这个条件来识别待解码的码流可能损坏。
在解码的过程中,还有一种是当前状态下面,输入的新的4个比特后,对应的转移弧还是转移到当前状态,在nginx中这种是用来表示当前状态不可能碰到这种组合的比特,也用来表示当前的输入码流可能已经损坏。
3. 源码分析
4. 如何构造状态转移表
<未完待续>