哈弗曼编码
首先说明一下涉及到的数据结构:
typedef struct ct_data {
union {
ush freq; /* frequency count */
ush code; /* bit string */
} fc;
union {
ush dad; /* father node in Huffman tree */
ush len; /* length of bit string */
} dl;
} ct_data;
(ush是unsigned short int类型)这是一个结构体里面套了两个联合体的数据结构。Gzip用这个ct_data类型定义了五棵树,其实就是数组:dyn_ltree[],dyn_dtree[],static_ltree[],static_dtree[],bl_tree[]。数组的下标表示待编码节点的值,freq字段表示该节点出现的频率,其余字段暂不介绍。
哈弗曼编码的输入
在gzip中,哈弗曼编码可看作是LZ77的下一级压缩,但哈弗曼编码并不直接处理LZ77输出的第一手材料,而是做了一步映射。
dyn_dtree对应d_buf,其数据的取值范围在0到32K之间,gzip将这32K的范围映射到[0, 29]。因为树数组的下标代表节点的取值,所以dyn_dtree数组的长度可以定为30。要是不做映射,dyn_dtree数组的大小就需要32K之多,而且其中很多节点可能频率为0。
dyn_ltree对应l_buf。l_buf中literal和length是混杂的,且两者的取值范围都是0到255,完全重叠在一起,如果直接哈弗曼编码,解压时,除非参照flag_buf,否则无法区分。之前第二篇计算最小匹配长度时,曾假设(距离-长度)二元组在上下文中能自说明,不需要额外的描述符(如flag_buf)。为了让假设成真,gzip将length映射到[257, 285]这29个节点(用256表示block结束),所以解压时,值大于256的是length,小于256的是literal。所以,dyn_ltree数组需要285的大小存放literal和length节点。
那么这个映射是怎么做的呢?这样多对一的映射,解压时又该怎么还原呢?
映射的方法就是将原数值的后几位比特(称为extra bit)截断,这样就映射到了比较小的值了。解压时,先解码映射后的小数值,被截断的比特紧随其后,直接拷贝就行了,具体要拷贝多少比特视小数值而定:
int extra_dbits[30] /* extra bits for each distance code */
= {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
这是距离extra bit长度的定义;
int extra_lbits[29] /* extra bits for each length code */
= {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
这是长度extra bit长度的定义。