以下内容包含我对RFC1951部分内容的翻译与总结,不足之处还请大家指出,小弟感激不尽。
在压缩中,哈夫曼编码有两种方式,分别是静态哈夫曼编码(Compression with fixed Huffman codes)和动态哈夫曼编码(Compressionwith dynamic Huffman codes),前者针对不同的字符有一张固定的编码表,压缩的时候直接按照这张码表对原始字符一一编码即可;后者会根据实际的压缩内容现场制定编码表,并根据这张编码表对待压缩内容编码。除此之外,还有一种,叫做“存储”,即不压缩,通常起到一个类似打包的作用,见下图,
注意,从这里开始,所有的分析内容都会按实际压缩文件内容来介绍,这里每一个字节,我们在读的时候,基本都要从右往左读,所有涉及到用二进制表示的地方,都要留心,当然,我会在对应的地方提示大家,后面的实例还会有更详细的分析。每个字节都是8bits。
*块首部
每一个压缩块都必须有一个块首部,但是每一个压缩块在最终输出的时候,下面三种编码格式(存储、静态、动态)只能用一种。块首部只用三比特(3bits)来表示,
第一比特 BFINAL
后续两比特 BTYPE
BFINAL这一位如果置上(该位置一),表示这是最后一个压缩块,如果没置上(该位是0),表示这个压缩块后面还有后续的压缩块。BTYPE这两位表示本块所使用的编码方式,具体定义如下,
十进制值 bits(二进制) 含义
000 no compression 没压缩
1 01 compressed with fixed Huffman codes,静态哈夫曼编码
2 10 compressed with dynamic Huffman codes,动态哈夫曼编码
3 11 reserved (error),预留,异常
上面BTYPE的这四种情况,在这里读的时候要从左往右读,按咱们平时的习惯读就行,解析实际的压缩结果时也要这样,如果看bits这列不习惯,那就以十进制值这列为主吧,BTYPE与码字的读取方法不同,切记切记,小弟在此吃过亏。如下图所示,
图a和图b都使用了gzip压缩,不同的是,前者使用静态哈夫曼编码,后者使用动态哈夫曼编码。图a中(00000000h,a)位置就是deflate压缩数据的开始,图b中(00000010h,2)就是deflate压缩数据的开始,他们对应的值分别为“0x4B”和“0x75”,转换成二进制就是“0100 1011”和“0111 0101”,这两个字节,在读的时候,要从右往左读,1是BFINAL,表明这是最后一个压缩块;“01”和“10”这两位,表示BTYPE,但是单独读这两位的时候,就不能再从右往左读了,而是按照这两位表示的实际值去读,前者是十进制的1,后者是十进制的2,前者代表静态哈夫曼编码,后者代表动态哈夫曼编码,即,图a对应静态哈夫曼编码,图b对应动态哈夫曼编码。BTYPE的读取方式千万千万要注意,要小心,我在这上面已经栽过好几次跟头了。
deflate通过将定长的编码重新编码从而实现了压缩,如果之前被编码内容每个字符占一个字节,那么经过压缩后,这些字符对应码字的码字长度就不一定了,所有码字组合起来成为了二进制码流,这个时候,字节只能用于表示整个压缩文件的大小而不能代表这个压缩文件中的每一个码字了。这就好比方砖和拼图,字节就是方砖;再举个例子,如下图所示,
压缩后的内容要当成码流,更准确的说是比特流而不再是字节流。这个概念一定要有,后续分析源码的时候直接影响到对源码的理解。压缩结果是按块输出的,每个压缩块实际上就是一段比特流,每块所占总比特数,多数情况下都不是一个字节的整数倍,就像上图中的例子。因为有可能会分成很多块输出,所以,块首部不一定会出现在一个字节的边界处(这个字节的第三位或高三位),也有可能出现在一个字节的中间,当然,第一块(从一开始而不是从零开始)的块首部肯定出现在字节的边界(因为第一块的前面是gzip文件头,gzip文件头是以字节为单位的)。