﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-swo2006-文章分类-数据压缩</title><link>http://www.cppblog.com/swo2006/category/3205.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 20 May 2008 04:20:47 GMT</lastBuildDate><pubDate>Tue, 20 May 2008 04:20:47 GMT</pubDate><ttl>60</ttl><item><title>无损数据压缩</title><link>http://www.cppblog.com/swo2006/articles/11520.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Mon, 21 Aug 2006 06:20:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11520.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11520.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11520.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11520.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11520.html</trackback:ping><description><![CDATA[
		<font color="#000000">数据压缩的起源要比计算机的起源早得多，数据压缩技术在计算机技术的萌芽时期就已经被提上了议事日程，军事科学家、数学家、电子学家一直在研究有关信息如何被高效存储和传递的问题。随着信息论的产生和发展，数据压缩也由热门话题演变成了真正的技术。<br /><br />数据压缩可分成两种类型，一种叫做无损压缩，另一种叫做有损压缩。<br />无
损压缩是指使用压缩后的数据进行重构(或者叫做还原，解压缩)，重构后的数据与原来的数据完全相同；无损压缩用于要求重构的信号与原始信号完全一致的场
合。磁盘文件的压缩就是一个很常见的例子。根据目前的技术水平，无损压缩算法一般可以把普通文件的数据压缩到原来的1/2～1/4。<br />有损压缩是指使用压缩后的数据进行重构，重构后的数据与原来的数据有所不同，但不会让人对原始资料<br />表
达的信息造成误解。有损压缩适用于重构信号不一定非要和原始信号完全相同的场合。例如，图像和声音的压缩就可以采用有损压缩，因为其中包含的数据往往多于
我们的视觉系统和听觉系统所能接收的信息，丢掉一些数据而不至于对声音或者图像所表达的意思产生误解，但可大大提高压缩比。<br /><br />压缩技术大致可以按照以下的方法分类：<br />                                                                                       压缩技术<br />                                                                                               |<br />                                                                         /------------------------------\<br />                                                        通用无损数据压缩             多媒体数据压缩(大多为有损压缩)<br />                                                                      
|                                                         |<br />                                                          /----------------\ 
                 /------------------------------------\<br />                                                  基于统计          基于字典   音频压缩         图像压缩               视频压缩<br />                                                  
模型的压         模型的压         |                         |
                                |<br />                                                  缩技术             缩技术         MP3等     /-------------------\            AVI<br />                                                        |
                        |                               二值 灰度 彩色
矢量       MPEG2等<br />                                                    /------\         /-------------\                    图像 图像 图像 图像<br />                                            Huffman 算术 LZ77 LZ78 LZW                      |         |          |        \ <br />                                            编码     编码     \-------------/                传真机 FELICS GIF         PostScript<br />                                                |
            |                     |                         标准
    JPEG等 JPEG等 Windows WMF等<br />                                   UNIX下           接近无损       PKZIP、LHarc、ARJ、<br />                                   的COMPACT  压缩极限        UNIX下的COMPRESS<br />                                   程序等             的高级应用    程序等<br /><br /><br />通用无损数据压缩的历史<br />科学家在研究中发现，大多数信息的表达都存在着一定的冗余度，通过采用一定的模型和编码方法，可以<br />降低这种冗余度。贝尔实验室的 Claude Shannon 和 MIT 的 R.M.Fano 几乎同时提出了最早的对符号进行有<br />效编码从而实现数据压缩的 Shannon-Fano 编码方法。<br /><br />D.A.Huffman
于1952年第一次发表了他的论文“最小冗余代码的构造方法”(A Method for the Construction of Minimum
Redundancy Codes)。从此，数据压缩开始在商业程序中实现并被应用在许多技术领域。UNIX 系统上一个压缩程序 COMPACT
就是 Huffman 0 阶自适应编码的具体实现。80 年代初，Huffman编码又在CP/M 和DOS 系统中实现，其代表程序叫
SQ。在数据压缩领域，Huffman 的这一论文事实上开创了数据压缩技术一个值得回忆的时代，60 年代、70 年代乃至 80
年代的早期，数据压缩领域几乎一直被 Huffman 编码及其分支所垄断。如果不是后面将要提到的那两个以色列人，也许我们今天还要在
Huffman编码的 0 和 1 的组合中流连忘返。<br /><br />80年代，数学家们不满足于 Huffman
编码中的某些致命弱点，他们从新的角度入手，遵循 Huffman
编码的主导思想，设计出另一种更为精确，更能接近信息论中“熵”极限的编码方法——算术编码。凭借算术编码的精妙设计和卓越表现，人们终于可以向着数据压
缩的极限前进了。可以证明，算术编码得到的压缩效果可以最大地减小信息的冗余度，用最少量的符号精确表达原始信息内容。当然，算术编码同时也给程序员和计
算机带来了新的挑战：要实现和运行算术编码，需要更为艰苦的编程劳动和更加快速的计算机系统。也就是，在同样的计算机系统上，算术编码虽然可以得到最好的
压缩效果，但却要消耗也许几十倍的计算时。这就是为什么算术编码不能在我们日常使用的压缩工具中实现的主要原因。<br /><br />那么，能不能既在压缩效
果上超越 Huffman，又不增加程序对系统资源和时间的需求呢？我们必须感谢下面将要介绍的两个以色列人。直到 1977
年，数据压缩的研究工作主要集中于熵、字符和单词频率以及统计模型等方面，研究者们一直在绞尽脑汁为使用Huffman编码的程序找出更快、更好的改进方
法。1977 年以后，一切都改变了。<br /><br />1977 年，以色列人 Jacob Ziv 和 Abraham Lempel
发表了论文“顺序数据压缩的一个通用算法”(A Universal Alogrithem for Sequential Data
Compression)。1978 年，他们发表了该论文的续篇“通过可变比率编码的独立序列的压缩”(Compression of
Individual Sequences via Variable-Rate Coding)。在这两篇论文中提出的压缩技术分别被称为 LZ77
和 LZ78 (不知为什么，作者名字的首字母被倒置了)。简单地说，这两种压缩方法的思路完全不同于从 Shannon 到 Huffman
到算术压缩的传统思路，人们将基于这一思路的编码方法称作“字典”式编码。字典式编码不但在压缩效果上大大超过了
Huffman，而且，对于好的实现，其压缩和解压缩的速度也异常惊人。<br /><br />1984 年，Terry Welch
发表了名为“高性能数据压缩技术”(A Technique for High-Performance Data
Compression)的论文，描述了他在 Sperry Research
Center(现在是Unisys的一部分)的研究成果。他实现了LZ78 算法的一个变种 —— LZW。LZW 继承了 LZ77 和 LZ78
压缩效果好、速度快的优点，而且在算法描述上更容易被人们接受（有的研究者认为是由于 Welch 的论文比 Ziv 和 Lempel
的更容易理解），实现也比较简单。不久，UNIX 上出现了使用 LZW 算法的 Compress
程序，该程序性能优良，并有高水平的文档，很快成为了 UNIX 世界的压缩程序标准。紧随其后的是 MS-DOS 环境下的ARC程序(
System Enhancement Associates, 1985 )，还有象 PKWare、PKARC
等仿制品。LZ78和LZW一时间统治了UNIX和DOS两大平台。<br /><br />80 年代中期以后，人们对 LZ77
进行了改进，随之诞生了一批我们今天还在大量使用的压缩程序。Haruyasu Yoshizaki(Yoshi)的LHarc和Robert
Jung的ARJ是其中两个著名的例子。LZ77得以和LZ78、LZW一起垄断当今的通用数据压缩领域。<br /><br />目前，基于字典方式的压缩已经有了一个被广泛认可的标准，从古老的PKZip到现在的WinZip，特别是随<br />着Internet上文件传输的流行，ZIP 格式成为了事实上的标准，没有哪一种通用的文件压缩、归档系统不支<br />持 ZIP 格式。本章主要介绍目前用得最多和技术最成熟的无损压缩编码技术，包括包含霍夫曼(Huffman)编码、算术编码、RLE编码和词典编码 。注意有一部分压缩算法受到美国专利法的保护（例如 LZW 算法的某些部分和高阶算术压缩算法的某些细节等）。<br /><br />4.1 仙农-范诺与霍夫曼编码<br />4.1.1 仙农-范诺（Shannon-Fano）编码<br />仙农-范诺编码算法需要用到下面两个基本概念：<br />1. Entropy(熵)<br />(1) 熵是信息量的度量方法，表示一条信息中真正需要编码的信息量。事件发生的可能性越小（数学上就<br />是概率越小），表示某一事件出现的消息越多。<br />(2) 某个事件的信息量用Ii=-log2 pi表示（有时称为surprise）， 其中pi为第i个事件的概率，0 pi 1对数以2为底时，熵的单位是"bits"。<br />2. 信源S的熵<br />按照仙农(Shannon)的理论，信源S的熵定义为<br />其中pi是符号si在S中出现的概率；log2(1/ pi)表示包含在si中的信息量，也就是编码si所需要的位数。<br />例如，一幅用256级灰度表示的图像，如果每一个象素点灰度的概率均为pi=1/256，编码每一个象素点就需<br />要8位。（最大熵分布）<br />最小熵分布： 除了一个符号外其余符号的概率全为0，H＝0bits.（定义0log20＝0）<br /><br />例如，对下面这条只出现了 a b c 三个字符的字符串：aabbaccbaa，字符串长度为 10，字符 a b c 分<br />别出现了 5 3 2 次，则 a b c 在信息中出现的概率分别为 0.5 0.3 0.2，他们的熵分别为：<br />Ea = -log2(0.5) = 1<br />Eb = -log2(0.3) = 1.737<br />Ec = -log2(0.2) = 2.322<br />整条信息的熵也即表达整个字符串需要的位数为：<br />Ea * 5 + Eb * 3 + Ec * 2 = 14.855 位<br />如果用计算机中常用的 ASCII 编码，表示上面的字符串需要整整80位！信息为什么能被压缩而不丢失原<br />有的信息内容呢？简单地讲，用较少的位数表示较频繁出现的符号，这就是数据压缩的基本准则。（怎样用 0<br />1 这样的二进制数码表示零点几个二进制位呢？确实很困难，但不是没有办法。一旦找到了准确表示零点几个<br />二进制位的方法，就接近无损压缩的极限了。）<br />[例4.1] 有一幅40个象素组成的灰度图像，灰度共有5级，分别用符号A、B、C、D和E表示，40个象素中出<br />现灰度A的象素数有15个，出现灰度B的象素数有7个，出现灰度C的象素数有7个等等，如表4-01所示。<br />如果用3个位表示这5个等级的灰度值，也就是每个象素用3位表示(等长编码)，编码这幅图像总共需要120<br />位。<br />表4-01 符号在图像中出现的数目<br />按照仙农理论，这幅图像的熵为 H(S)=(15/40)×log2(40/15) + (7/40)×log2(40/7) +… + (5/40)<br />×log2(40/5) = 2.196<br />这就是说每个符号用2.196位表示，40个象素需用87.84位。<br />最早阐述和实现这种编码的是Shannon(1948年)和Fano(1949年)，因此被称为仙农-范诺(Shannon-Fano)算<br />法。这种方法采用从上到下的方法进行编码。<br />首先按照符号出现的频度或概率排序，例如，A，B，C，D和E，如表4-02所示。<br />然后使用递归方法分成两个部分，每一部分具有近似相同的次数，如图4-01所示。<br />按照这种方法进行编码得到的总位数为91，实际的压缩比约为1.3 : 1。<br />表4-02 Shannon-Fano算法举例表<br />符 号 A B C D E<br />出现的次数 15 7 7 6 5<br />符号 出现的次数(pi) log2(1/pi) 分配的代码 需要的位数<br />A 15 (0.375) 1.4150 00 30<br />B 7 (0.175) 2.5145 01 14<br />C 7 (0.175) 2.5145 10 14<br />D 6 (0.150) 2.7369 110 18<br />E 5 (0.125) 3.0000 111 15<br />第 4 页<br />4<br />图4-01 仙农-范诺算法编码举例<br />4.1.2 霍夫曼(Huffman)编码<br />霍夫曼在1952年提出了另一种编码方法，即从下到上的编码方法。现以一个具体的例子说明它的编码步<br />骤：<br />(1) 初始化，根据符号概率的大小按由大到小顺序对符号进行排序，如表4-03和图4-02所示。<br />(2) 把概率最小的两个符号组成一个节点，如图4-02中的D和E组成节点P1。<br />(3) 重复步骤2，得到节点P2、P3和P4，形成一棵“树”，其中的P4称为根节点。<br />(4) 从根节点P4开始到相应于每个符号的“树叶”，从上到下标上“0”(上枝)或者“1”(下枝)，至于哪<br />个为“1”哪个为“0”则无关紧要，最后的结果仅仅是分配的代码不同，而代码的平均长度是相同的。<br />(5) 从根节点P4开始顺着树枝到每个叶子分别写出每个符号的代码，如表4-03所示。<br />(6) 按照仙农理论，这幅图像的熵为<br />H(S)=(15/39)×log2(39/15) + (7/39)×log2(39/7) + … + (5/39)×log2(39/5) = 2.1859<br />压缩比1.37:1。<br />表4-03 霍夫曼编码举例<br />图4-02 霍夫曼编码方法<br />霍夫曼码的码长虽然是可变的，但却不需要另外附加同步代码（前缀代码）。例如，码串中的第1位为0，<br />那末肯定是符号A，因为表示其他符号的代码没有一个是以0开始的，因此下一位就表示下一个符号代码的第1<br />符号 出现的次数(pi) log2(1/pi) 分配的代码 需要的位数<br />A 15(0.3846) 1.38 0 15<br />B 7(0.1795) 2.48 100 21<br />C 6(0.1538) 2.70 101 18<br />D 6(0.1538) 2.70 110 18<br />E 5(0.1282) 2.96 111 15<br />第 5 页<br />4<br />位。同样，如果出现“110”，那么它就代表符号D。如果事先编写出一本解释各种代码意义的“词典”，即码<br />簿，那么就可以根据码簿一个码一个码地依次进行译码。<br />与仙农-范诺编码相同，这两种方法都自含同步码，在编码之后的码串中不需要另外添加标记符号（即在<br />译码时分割符号的特殊代码）。<br />采用霍夫曼编码时有两个问题值得注意：<br />①霍夫曼码没有错误保护功能，在译码时，如果码串中没有错误，那么就能一个接一个地正确译出代码。<br />但如果码串中有错误，哪怕仅仅是1位出现错误，不但这个码本身译错，更糟糕的是一错一大串，全乱了套，<br />这种现象称为错误传播(error propagation)。计算机对这种错误也无能为力，说不出错在哪里，更谈不上去<br />纠正它。<br />②霍夫曼码是可变长度码，因此很难随意查找或调用压缩文件中间的内容，然后再译码，这就需要在存储<br />代码之前加以考虑。<br />尽管如此，霍夫曼码还是得到广泛应用。 霍夫曼编码方法的编码效率比仙农-范诺编码效率高一些。<br />4.2 算术编码<br />算术编码在图像数据压缩标准(如JPEG，JBIG)中扮演了重要的角色。在算术编码中，消息用0到1之间的实<br />数进行编码，算术编码用到两个基本的参数：符号的概率和编码间隔。信源符号的概率决定压缩编码的效率，<br />也决定编码过程中信源符号的间隔，而这些间隔包含在0到1之间。编码过程中的间隔决定了符号压缩后的输<br />出。算术编码器的编码过程可用下面的例子加以解释。<br />[例4.2] 假设信源符号为{00, 01, 10, 11}，这些符号的概率分别为{ 0.1, 0.4, 0.2, 0.3 }，根据这些<br />概率可把间隔[0, 1)分成4个子间隔：[0, 0.1), [0.1, 0.5), [0.5, 0.7), [0.7, 1)，其中[x,y)表示半开放<br />间隔，即包含x不包含y。上面的信息可综合在表4-04中。<br />表4-04 信源符号，概率和初始编码间隔<br />如果二进制消息序列的输入为：10 00 11 00 10 11 01。编码时首先输入的符号是10，找到它的编码范围<br />是[0.5, 0.7)。消息中第二个符号00的编码范围是[0, 0.1)，因此就取[0.5, 0.7)的第一个十分之一作为新间<br />隔[0.5, 0.52)。依此类推，编码第3个符号11时取新间隔为[0.514, 0.52)，编码第4个符号00时，取新间隔为<br />[0.514, 0.5146)，… 。消息的编码输出可以是最后一个间隔中的任意数。整个编码过程如图4-03所示。<br />符号 00 01 10 11<br />概率 0.1 0.4 0.2 0.3<br />初始编码间隔 [0, 0.1) [0.1, 0.5) [0.5, 0.7) [0.7, 1)<br />第 6 页<br />4<br />图4-03 算术编码过程举例<br />这个例子的编码和译码的全过程分别表示在表4-05和表4-06中。<br />根据上面所举的例子，可把计算过程总结如下。<br />考虑一个有M个符号i=(1,2,…,M)的字符表集，假设概率p( i)=pi，而<br />。输入符号用xn表示，第n个子间隔的范围用<br />表示。其中l0=0，d0=1和p0=0，ln表示间隔左边界的值,rn 表示间<br />隔右边界的值，dn=rn-ln表示间隔长度。编码步骤如下：<br />步骤1：首先在1和0之间给每个符号分配一个初始子间隔，子间隔的长度等于它的概率，初始子间隔的范<br />围用I1=[l1，r1)=[ ， )表示。令d1=r1-l1，L=l1和R=r1。<br />步骤2：L和R的二进制表达式分别表示为：<br />和<br />其中ui 和vi 等于“1”或者“0”。<br />①如果u1<br />≠v1 ，不发送任何数据，转到步骤3；<br />②如果u1=v1，就发送二进制符号u1。<br />比较u2<br />和v2：如果u2≠v2 ，不发送任何数据，转到步骤3；<br />如果u2=v2，就发送二进制符号u2。<br />…<br />这种比较一直进行到两个符号不相同为止，然后进入步骤3。<br />步骤3：n加1，读下一个符号。假设第n个输入符号为xn= i，按照以前的步骤把这个间隔分成如下所示的<br />子间隔：<br />第 7 页<br />4<br />令L=ln，R=rn 和 dn=rn-ln，然后转到步骤2。<br />表4-05 编码过程<br />表4-06 译码过程<br />[例4.3] 假设有4个符号的信源，它们的概率如表4-07所示：<br />表4-07 符号概率<br />输入序列为xn： 2， 1， 3，…。它的编码过程如图4-04所示，现说明如下。<br />输入第1个符号是x1= 2，可知i=2，定义初始间隔＝[0.5, 0.75)，由此可知<br />d1=0.25，左右边界的二进制数分别表示为：L＝0.5=0.1(B)，R＝0.7＝0.11… (B) 。按照步骤2，u1=v1，发<br />送1。因u2≠v2，因此转到步骤3。<br />输入第2个字符x2= 1，i=1，它的子间隔＝[0.5, 0.625)，由此可<br />得d2=0.125。左右边界的二进制数分别表示为：L＝0.5=0.100 … (B)，R＝0.101… (B)。按照步骤2，<br />u2=v2=0，发送0，而u3和v3不相同，因此在发送0之后就转到步骤3。<br />输入第3个字符，x3= 3，i=3，它的子间隔＝[0.59375, 0.609375)<br />，由此可得d3=0.015625。左右边界的二进制数分别表示为：L＝0.59375=0.10011 (B)，R＝<br />步骤 输入符号 编码间隔 编码判决<br />1 10 [0.5, 0.7) 符号的间隔范围[0.5, 0.7)<br />2 00 [0.5, 0.52) [0.5, 0.7)间隔的第一个1/10<br />3 11 [0.514, 0.52) [0.5, 0.52)间隔的最后三个1/10<br />4 00 [0.514, 0.5146) [0.514, 0.52)间隔的第一个1/10<br />5 10 [0.5143, 0.51442) [0.514, 0.5146)间隔的第五个1/10开始，二个1/10<br />6 11 [0.514384, 0.51442 [0.5143, 0.51442)间隔的最后3个1/10<br />7 01 [0.5143836, 0.514402) [0.514384, 0.51442)间隔的4个1/10，从第1个1/10开始<br />8 从[0.5143876, 0.514402)中选择一个数作为输出：0.5143876<br />步骤 间隔 译码符号译码判决<br />1 [0.5, 0.7) 10 0.51439在间隔 [0.5, 0.7)<br />2 [0.5, 0.52) 00 0.51439在间隔 [0.5, 0.7)的第1个1/10<br />3 [0.514, 0.52) 11 0.51439在间隔[0.5, 0.52)的第7个1/10<br />4 [0.514, 0.5146) 00 0.51439在间隔[0.514, 0.52)的第1个1/10<br />5 [0.5143, 0.51442) 10 0.51439在间隔[0.514, 0.5146)的第5个1/10<br />6 [0.514384, 0.51442) 11 0.51439在间隔[0.5143, 0.51442)的第7个1/10<br />7 [0.51439, 0.5143948) 01 0.51439在间隔[0.51439, 0.5143948)的第1个1/10<br />8 译码的消息：10 00 11 00 10 11 01<br />信源符号ai 1 2 3 4<br />概率pi p1=0.5 p2=0.25 p3=0.125 p4=0.125<br />初始编码间隔 [0, 0.5) [0.5, 0.75) [0.75, 0.875) [0.875, 1)<br />第 8 页<br />4<br />0.609375=0.100111 (B)。按照步骤2，u3=v3=0，u4=v4=1，u5=v5=1，但u6和v6不相同，因此在发送011之后转到<br />步骤3。<br />…<br />发送的符号是：10011…。被编码的最后的符号是结束符号。<br />图4-04 算术编码概念<br />就这个例子而言，算术编码器接受的第1位是“1”，它的间隔范围就限制在[0.5, 1)，但在这个范围里有<br />3种可能的码符2， 3和4，因此第1位没有包含足够的译码信息。在接受第2位之后就变成“10”，它落在<br />[0.5, 0.75)的间隔里，由于这两位表示的符号都指向2开始的间隔，因此就可断定第一个符号是2。在接受<br />每位信息之后的译码情况如下表4-08所示。<br />表4-08 译码过程表<br />在上面的例子中，我们假定编码器和译码器都知道消息的长度，因此译码器的译码过程不会无限制地运行<br />下去。实际上在译码器中需要添加一个专门的终止符，当译码器看到终止符时就停止译码。<br />在算术编码中需要注意的几个问题：<br />(1) 由于实际的计算机的精度不可能无限长，运算中出现溢出是一个明显的问题，但多数机器都有16位、<br />32位或者64位的精度，因此这个问题可使用比例缩放方法解决。<br />(2) 算术编码器对整个消息只产生一个码字，这个码字是在间隔[0, 1)中的一个实数，因此译码器在接受<br />到表示这个实数的所有位之前不能进行译码。<br />(3) 算术编码也是一种对错误很敏感的编码方法，如果有一位发生错误就会导致整个消息译错。<br />算术编码可以是静态的或者自适应的。在静态算术编码中，信源符号的概率是固定的。在自适应算术编码<br />中，信源符号的概率根据编码时符号出现的频繁程度动态地进行修改，在编码期间估算信源符号概率的过程叫<br />做建模。需要开发动态算术编码的原因是因为事先知道精确的信源概率是很难的，而且是不切实际的。当压缩<br />消息时，我们不能期待一个算术编码器获得最大的效率，所能做的最有效的方法是在编码过程中估算概率。因<br />此动态建模就成为确定编码器压缩效率的关键。<br />接受的数字 间隔 译码输出<br />1 [0.5, 1) -<br />0 [0.5, 0.75) 2<br />0 [0.5, 0.609375) 1<br />1 [0.5625, 0.609375) -<br />1 [0.59375, 0.609375) 3<br />… … …<br />第 9 页<br />4<br />4.3 RLE编码<br />在一幅图像中经常包含有许多颜色相同的图块。在这些图块中，许多行上都具有相同的颜色，或者在一行<br />上有许多连续的像素都具有相同的颜色值。在这种情况下就不需要存储每一个像素的颜色值，而仅仅存储一个<br />像素的颜色值，以及具有相同颜色的像素数目就可以，或者存储一个像素的颜色值，以及具有相同颜色值的行<br />数。这种压缩编码称为行程编码(run length encoding，RLE)，具有相同颜色并且是连续的像素数目称为行程<br />长度。<br />假定有一幅灰度图像，第n行的像素值如图4-05所示：<br />图4-05 RLE编码的概念<br />用RLE编码方法得到的代码为：80315084180。代码中用黑体表示的数字是行程长度，黑体字后面的数字代<br />表像素的颜色值。例如黑体字50代表有连续50个像素具有相同的颜色值，它的颜色值是8。<br />对比RLE编码前后的代码数可以发现，在编码前要用73个代码表示这一行的数据，而编码后只要用11个代<br />码表示代表原来的73个代码，压缩前后的数据量之比约为7:1，即压缩比为7:1。这说明RLE确实是一种压缩技<br />术，而且这种编码技术相当直观，也非常经济。<br />译码时按照与编码时采用的相同规则进行，还原后得到的数据与压缩前的数据完全相同。<br />RLE所能获得的压缩比有多大，这主要是取决于图像本身的特点。如果图像中具有相同颜色的图像块越<br />大，图像块数目越少，获得的压缩比就越高。反之，压缩比就越小。<br />RLE压缩编码尤其适用于计算机生成的图像，对减少图像文件的存储空间非常有效。然而，RLE对颜色丰富<br />的自然图像就显得力不从心，在同一行上具有相同颜色的连续像素往往很少，而连续几行都具有相同颜色值的<br />连续行数就更少。如果仍然使用RLE编码方法，不仅不能压缩图像数据，反而可能使原来的图像数据变得更<br />大。请注意，这并不是说RLE编码方法不适用于自然图像的压缩，相反，在自然图像的压缩中还真少不了RLE，<br />只不过是不能单纯使用RLE一种编码方法，需要和其他的压缩编码技术联合应用。<br />4.4 词典编码<br />有许多场合，开始时不知道要编码数据的统计特性，也不一定允许你事先知道它们的统计特性。因此，人<br />们提出了许许多多的数据压缩方法，尽可能获得最大的压缩比。这些技术统称为通用编码技术。词典编码<br />(Dictionary Encoding)技术就属于这一类。<br />4.4.1 词典编码的思想<br />词典编码(dictionary encoding)的根据是数据本身包含有重复代码这个特性。例如文本文件和光栅图像<br />就具有这种特性。词典编码法的种类很多，归纳起来大致有两类。<br />第一类词典算法是企图查找正在压缩的字符序列是否在以前输入的数据中出现过，然后输出仅仅是指向早<br />期出现过的字符串的“指针”。这种编码概念如图4-06所示。<br />第 10 页<br />4<br />图4-06 第一类词典法编码概念<br />这里所指的“词典”是指用以前处理过的数据来表示编码过程中遇到的重复部分。这类编码算法都是以<br />Abraham Lempel和Jakob Ziv在1977年开发和发表的称为LZ77算法为基础的，例如1982年由Storer和Szymanski<br />改进的称为LZSS算法 。<br />第二类词典算法是企图从输入的数据中创建一个“短语词典(dictionary of the phrases)”，这种短语<br />不一定是具有具体含义的短语，可以是任意字符的组合。编码过程中遇到已经在词典中出现的“短语”时，编<br />码器就输出这个词典中的短语的“索引号”，而不是短语本身。这个概念如图4-07所示。<br />图4-07 第二类词典法编码概念<br />J.Ziv和A.Lempel在1978年首次发表了介绍这种编码方法的文章。在他们的研究基础上，Terry A.Weltch<br />在1984年发表了改进这种编码算法的文章，因此把这种编码方法称为LZW(Lempel-Ziv Walch)压缩编码，在高<br />速硬盘控制器上 首先应用了这种算法。<br />4.4.2 LZ77算法<br />为了更好地说明LZ77算法的原理，首先介绍算法中用到的几个术语：<br />(1) 输入数据流(input stream)：要被压缩的字符序列。<br />(2) 字符(character)：输入数据流中的基本单元。<br />(3) 编码位置(coding position)：输入数据流中当前要编码的字符位置，指前向缓冲存储器中的开始字<br />符。<br />(4) 前向缓冲存储器(Lookahead buffer)：存放从编码位置到输入数据流结束的字符序列的存储器。<br />(5) 窗口(window)：指包含W个字符的窗口，字符是从编码位置开始向后数，也就是最后处理的W个字<br />符 。(滑动窗口)<br />(6) 指针(pointer)：指向窗口中的匹配串的开始位置且含长度的指针。<br />LZ77编码算法的核心是查找从前向缓冲存储器开始的与窗口中最长的匹配串。编码算法的具体执行步骤如<br />下：<br />第 11 页<br />4<br />(1) 把编码位置设置到输入数据流的开始位置。<br />(2) 查找窗口中最长的匹配串。<br />(3) 以“(Pointer, Length) Character”三元组的格式输出，其中Pointer是指向窗口中匹配串的指针，<br />Length表示匹配字符的长度，Characters是前向缓冲存储器中的不匹配的第1个字符。没有匹配的字符串时，<br />输出“(0, 0) Character”<br />(4) 如果前向缓冲存储器不是空的，则把编码位置和窗口向前移(Length+1)个字符，然后返回到步骤2。<br />[例4.4] 待编码的数据流如表4-09所示，编码过程如表4-10所示。现作如下说明：<br />(1) “步骤”栏表示编码步骤。<br />(2) “位置”栏表示编码位置，输入数据流中的第1个字符为编码位置1。<br />(3) “匹配串”栏表示窗口中找到的最长的匹配串。<br />(4) “字符”栏表示匹配之后在前向缓冲存储器中的第1个字符。<br />(5) “输出”栏以“(Back_chars, Chars_length) Explicit_character”格式输出。其中，<br />(Back_chars, Chars_length)是指向匹配串的指针，告诉译码器“在这个窗口中向后退Back_chars个字符然后<br />拷贝Chars_length个字符到输出”，Explicit_character是真实字符。例如，表4-10中的输出“(5,2) C”告<br />诉译码器回退5个字符，然后拷贝2个字符“AB”<br />表4-09待编码的数据流<br />表4-10 编码过程<br />4.4.3 LZSS算法<br />LZ77通过输出真实字符解决了在窗口中出现没有匹配串的问题，但这个解决方案包含有冗余信息。冗余信<br />息表现在两个方面，一是空指针，二是编码器输出的字符可能包含在下一个匹配串中的字符。<br />LZSS算法以比较有效的方法解决这个问题，思想是如果匹配串的长度比指针本身的长度 （最小匹配串长<br />度）长就输出指针，否则就输出真实字符。由于输出的压缩数据流中包含有指针和字符本身，为了区分它们就<br />需要有额外的标志位，即ID位。<br />LZSS编码算法的具体执行步骤如下：<br />(1) 把编码位置置于输入数据流的开始位置。<br />(2) 在前向缓冲存储器中查找与窗口中最长的匹配串<br />① Pointer ：=匹配串指针。<br />② Length ：=匹配串长度。<br />(3) 判断匹配串长度Length是否大于等于最小匹配串长度(Length≥MIN_LENGTH)，<br />如果“是”：输出指针，然后把编码位置向前移动Length个字符。<br />位置 1 2 3 4 5 6 7 8 9<br />字符 A A B C B B A B C<br />步骤 位置 匹配串 字符 输出<br />1 1 -- A (0,0) A<br />2 2 A B (1,1) B<br />3 4 -- C (0,0) C<br />4 5 B B (2,1) B<br />5 7 A B C (5,2) C<br />第 12 页<br />4<br />如果“否”：输出前向缓冲存储器中的第1个字符，然后把编码位置向前移动一个字符。<br />(4) 如果前向缓冲存储器不是空的，就返回到步骤2。<br />[例4.5] 编码字符串如表4-11所示，编码过程如表4-12所示。现说明如下：<br />(1) “步骤”栏表示编码步骤。<br />(2) “位置”栏表示编码位置，输入数据流中的第1个字符为编码位置1。<br />(3) “匹配”栏表示窗口中找到的最长的匹配串。<br />(4) “字符”栏表示匹配之后在前向缓冲存储器中的第1个字符。<br />(5) “输出”栏的输出为：<br />① 如果匹配串本身的长度Length≥MIN_LENGTH，输出指向匹配串的指针，格式为(Back_chars,<br />Chars_length)。该指针告诉译码器“在这个窗口中向后退Back_chars个字符然后拷贝Chars_length个字符到<br />输出”。<br />② 如果匹配串本身的长度Length≤MIN_LENGTH，则输出真实的匹配串。<br />表4-11 输入数据流<br />表4-12 编码过程(MIN_LENGTH = 2)<br />在相同的计算环境下，LZSS算法比LZ77可获得比较高的压缩比，而译码同样简单。这也就是为什么这种算<br />法成为开发新算法的基础，许多后来开发的文档压缩程序都使用了LZSS的思想。例如，PKZip, ARJ, LHArc和<br />ZOO等等，其差别仅仅是指针的长短和窗口的大小等有所不同。<br />LZSS同样可以和熵编码联合使用，例如ARJ就与霍夫曼编码联用，而PKZip则与Shannon-Fano联用，它的后<br />续版本也采用霍夫曼编码。<br />4.4.4 LZ78算法<br />在介绍LZ78算法之前，首先说明在算法中用到的几个术语和符号：<br />(1) 字符流(Charstream)：要被编码的数据序列。<br />(2) 字符(Character)：字符流中的基本数据单元。<br />(3) 前缀(Prefix)： 在一个字符之前的字符序列。<br />(4) 缀-符串(String)：前缀＋字符。<br />(5) 码字(Code word)：码字流中的基本数据单元，代表词典中的一串字符。<br />(6) 码字流(Codestream)： 码字和字符组成的序列，是编码器的输出。<br />(7) 词典(Dictionary)： 缀-符串表。按照词典中的索引号对每条缀-符串(String)指定一个码字(Code<br />位置 1 2 3 4 5 6 7 8 9 10 11<br />字符 A A B B C B B A A B C<br />步骤 位置 匹配串 输出<br />1 1 -- A<br />2 2 A A<br />3 3 -- B<br />4 4 B B<br />5 5 -- C<br />6 6 B B (3,2)<br />7 8 A A B (7,3)<br />8 11 C C<br />第 13 页<br />4<br />word)。<br />(8) 当前前缀(Current prefix)：在编码算法中使用，指当前正在处理的前缀，用符号P表示。<br />(9) 当前字符(Current character)：在编码算法中使用，指当前前缀之后的字符，用符号C表示。<br />(10) 当前码字(Current code word)： 在译码算法中使用，指当前处理的码字，用W表示当前码字，<br />String.W表示当前码字的缀-符串。<br />1. 编码算法<br />LZ78的编码思想是不断地从字符流中提取新的缀-符串(String)，通俗地理解为新“词条”，然后用“代<br />号”也就是码字(Code word)表示这个“词条”。这样一来，对字符流的编码就变成了用码字(Code word)去替<br />换字符流(Charstream)，生成码字流(Codestream)，从而达到压缩数据的目的。<br />在编码开始时词典是空的，不包含任何缀-符串(string)。在这种情况下编码器就输出一个表示空字符串<br />的特殊码字(例如“0”)和字符流中(Charstream)的第一个字符C，并把这个字符C添加到词典中作为一个由一<br />个字符组成的缀-符串(string)。在编码过程中，如果出现类似的情况，也照此办理。<br />在词典中已经包含某些缀-符串(String)之后，如果“当前前缀P +当前字符C”已经在词典中，就用字符C<br />来扩展这个前缀，这样的扩展操作一直重复到获得一个在词典中没有的缀-符串(String)为止。此时就输出表<br />示当前前缀P的码字(Code word)和字符C，并把P+C添加到词典中，然后开始处理字符流(Charstream)中的下一<br />个前缀。<br />LZ78编码器的输出是码字-字符(W,C)对，每次输出一对到码字流中，并用字符C扩展与码字W相对应的缀-<br />符串(String)，生成新的缀-符串(String)，然后添加到词典中。<br />LZ78编码的具体算法如下：<br />步骤1： 在开始时，词典和当前前缀P都是空的。<br />步骤2： 当前字符C ：= 字符流中的下一个字符。<br />步骤3： 判断P+C是否在词典中：<br />(1) 如果“是”：用C扩展P，让P ：= P+C ；<br />(2) 如果“否”：<br />① 输出与当前前缀P相对应的码字和当前字符C；<br />② 把字符串P+C 添加到词典中。<br />③ 令P ：= 空值。<br />(3) 判断字符流中是否还有字符需要编码<br />① 如果“是”：返回到步骤2。<br />② 如果“否”：若当前前缀P不是空的，输出相应于当前前缀P的码字，然后结束编码。<br />2. 译码算法<br />在译码开始时译码词典是空的，它将在译码过程中从码字流中重构。每当从码字流中读入一对码字-字符<br />(W,C)对时，码字就参考已经在词典中的缀-符串，然后把当前码字的缀-符串string.W 和字符C输出到字符流<br />(Charstream)，而把当前缀-符串(string.W+C)添加到词典中。在译码结束之后，重构的词典与编码时生成的<br />词典完全相同。<br />LZ78译码的具体算法如下：<br />步骤1： 在开始时词典是空的。<br />步骤2： 当前码字W ：= 码字流中的下一个码字。<br />步骤3： 当前字符C ：= 紧随码字之后的字符。<br />步骤4： 把当前码字的缀-符串(string.W)输出到字符流(Charstream)，然后输出字符C。<br />步骤5： 把string.W+C添加到词典中。<br />步骤6： 判断码字流中是否还有码字要译<br />第 14 页<br />4<br />(1) 如果“是”，就返回到步骤2。<br />(2) 如果“否”，则结束。<br />[例4.6] 编码字符串如表4-13所示，编码过程如表4-14所示。现说明如下：<br />(1) “步骤”栏表示编码步骤。<br />(2) “位置”栏表示在输入数据中的当前位置。<br />(3) “词典”栏表示添加到词典中的缀-符串，缀-符串的索引等于“步骤”序号。<br />(4) “输出”栏以(当前码字W, 当前字符C)简化为(W, C)的形式输出。<br />表4-13 编码字符串<br />表4-14 编码过程<br />与LZ77相比，LZ78的最大优点是在每个编码步骤中减少了缀-符串(String)比较的数目，而压缩率与LZ77<br />类似。<br />4.4.5 LZW算法<br />在LZW算法中使用的术语与LZ78使用的相同，仅增加了一个术语—前缀根(Root)，它是由单个字符组成的<br />缀-符串(String)。在编码原理上，LZW与LZ78相比有如下差别：<br />① LZW只输出代表词典中的缀-符串(String)的码字(code word)。这就意味在开始时词典不能是空的，它<br />必须包含可能在字符流出现中的所有单个字符，即前缀根(Root)。<br />② 由于所有可能出现的单个字符都事先包含在词典中，每次编码开始时都使用一个字符前缀(onecharacter<br />prefix)，因此在词典中增加的第1个缀-符串有两个字符。<br />现将LZW编码算法和译码算法介绍如下。<br />1. 编码算法<br />LZW编码是围绕称为词典的转换表来完成的。这张转换表存放称为前缀(Prefix)的字符序列，并为每个表<br />项分配一个码字(Code word)，或者叫做序号，如表4-15所示。这张转换表实际上是把8位ASCII字符集进行扩<br />充，增加的符号用来表示在文本或图像中出现的可变长度ASCII字符串。扩充后的代码可用9位、10位、11位、<br />12位甚至更多的位来表示。Welch的论文中用了12位，12位可以有4096个不同的12位代码，这就是说，转换表<br />有4096个表项，其中256个表项用来存放已定义的字符，剩下3840个表项用来存放前缀(Prefix)。<br />表4-15 词典<br />位置 1 2 3 4 5 6 7 8 9<br />字符 A B B C B C A B A<br />步骤 位置 词典 输出<br />1 1 A (0,A)<br />2 2 B (0,B)<br />3 3 B C (2,C)<br />4 5 B C A (3,A)<br />5 8 B A (2,A)<br />码字(Code word) 前缀(Prefix)<br />1<br />第 15 页<br />4<br />LZW编码器(软件编码器或硬件编码器)就是通过管理这个词典完成输入与输出之间的转换。LZW编码器的输<br />入是字符流(Charstream)，字符流可以是用8位ASCII字符组成的字符串，而输出是用n位(例如12位)表示的码<br />字流(Codestream)，码字代表单个字符或多个字符组成的字符串。<br />LZW编码器使用了一种很实用的分析(parsing)算法，称为贪婪分析算法(greedy parsing algorithm)。在<br />贪婪分析算法中，每一次分析都要串行地检查来自字符流(Charstream)的字符串，从中分解出已经识别的最长<br />的字符串，也就是已经在词典中出现的最长的前缀(Prefix)。用已知的前缀(Prefix)加上下一个输入字符C也<br />就是当前字符(Current character)作为该前缀的扩展字符，形成新的扩展字符串——缀-符串(String)：<br />Prefix+C。这个新的缀-符串(String)是否要加到词典中，还要看词典中是否存有和它相同的缀-符串String。<br />如果有，那么这个缀-符串(String)就变成前缀(Prefix)，继续输入新的字符，否则就把这个缀-符串(String)<br />写到词典中生成一个新的前缀(Prefix)，并分配给一个代码。<br />LZW编码算法的具体执行步骤如下：<br />步骤1： 开始时的词典包含所有可能的根(Root)，而当前前缀P是空的；<br />步骤2： 当前字符(C) ：=字符流中的下一个字符；<br />步骤3： 判断缀-符串P+C是否在词典中<br />(1) 如果“是”：P ：= P+C // (用C扩展P) ；<br />(2) 如果“否”<br />① 把代表当前前缀P的码字输出到码字流;<br />② 把缀-符串P+C添加到词典;<br />③ 令P ：= C //(现在的P仅包含一个字符C);<br />步骤4： 判断码字流中是否还有码字要译<br />(1) 如果“是”，就返回到步骤2；<br />(2) 如果“否”<br />① 把代表当前前缀P的码字输出到码字流;<br />② 结束。<br />LZW编码算法可用伪码表示。开始时假设编码词典包含若干个已经定义的单个码字。例如，256个字符的码<br />字，用伪码可以表示成：<br />… …<br />193 A<br />194 B<br />… …<br />255<br />… …<br />1305 abcdefxyF01234<br />… …<br />Dictionary[j] ← all n single-character， j＝1, 2， …，n<br />j ← n+1<br />Prefix ← read first Character in Charstream<br />while((C ← next Character)!=NULL)<br />第 16 页<br />4<br />2. 译码算法<br />LZW译码算法中还用到另外两个术语：<br />① 当前码字(Current code word)：指当前正在处理的码字，用cW表示，用string.cW表示当前缀-符串；<br />② 先前码字(Previous code word)：指先于当前码字的码字，用pW表示，用string.pW表示先前缀-符<br />串。<br />LZW译码算法开始时，译码词典与编码词典相同，它包含所有可能的前缀根(roots)。LZW算法在译码过程<br />中会记住先前码字(pW)，从码字流中读当前码字(cW)之后输出当前缀-符串string.cW，然后把用string.cW的<br />第一个字符扩展的先前缀-符串string.pW添加到词典中。<br />LZW译码算法的具体执行步骤如下：<br />步骤1： 在开始译码时词典包含所有可能的前缀根(Root)。<br />步骤2： cW ：= 码字流中的第一个码字。<br />步骤3： 输出当前缀-符串string.cW到码字流。<br />步骤4： 先前码字pW ：= 当前码字cW。<br />步骤5： 当前码字cW ：= 码字流中的下一个码字。<br />步骤6： 判断当前缀-符串string.cW是否在词典中<br />(1) 如果“是”，则：<br />① 把当前缀-符串string.cW输出到字符流。<br />② 把先前缀-符串string.pW + 当前前缀-符串string.cW的第一个字符C添加到词典。<br />(2) 如果“否”，则：<br />① 输出先前缀-符串string.pW + 先前缀-符串string.pW的第一个字符到字符流，<br />② 把它添加到词典中。<br />步骤7： 判断码字流中是否还有码字要译<br />(1) 如果“是”，就返回到步骤4。<br />(2) 如果“否”, 结束。<br />LZW译码算法可用伪码表示如下：<br />Codestream ← cW for Prefix<br />Dictionary[j] ← all n single-character， j＝1, 2， …，n<br />j ← n+1<br />cW ← first code from Codestream<br />Charstream ← Dictionary[cW]<br />pW ← cW<br />While((cW ← next Code word)!=NULL)<br />第 17 页<br />4<br />[例4.7] 编码字符串如表4-16所示，编码过程如表4-17所示。现说明如下：<br />(1) “步骤”栏表示编码步骤；<br />(2) “位置”栏表示在输入数据中的当前位置；<br />(3) “词典”栏表示添加到词典中的缀-符串，它的索引在括号中；<br />(4) “输出”栏表示码字输出。<br />表4-16 被编码的字符串<br />表4-17 LZW的编码过程<br />表4-18解释了译码过程。每个译码步骤译码器读一个码字，输出相应的缀-符串，并把它添加到词典中。<br />例如，在步骤4中，先前码字(2)存储在先前码字(pW)中，当前码字(cW)是(4)，当前缀-符串string.cW是输出<br />(“A B”)，先前缀-符串string.pW ("B")是用当前缀-符串string.cW ("A")的第一个字符，其结果("B A")<br />添加到词典中，它的索引号是(6)<br />表4-18 LZW的译码过程<br />LZW算法得到普遍采用，它的速度比使用LZ77算法的速度快，因为它不需要执行那么多的缀-符串比较操<br />位置 1 2 3 4 5 6 7 8 9<br />字符 A B B A B A B A C<br />步骤 位置 词典 输出<br />(1) A<br />(2) B<br />(3) C<br />1 1 (4) A B (1)<br />2 2 (5) B B (2)<br />3 3 (6) B A (2)<br />4 4 (7) A B A (4)<br />5 6 (8) A B A C (7)<br />6 -- -- -- (3)<br />步骤 代码 词典 输出<br />(1) A<br />(2) B<br />(3) C<br />1 (1) -- -- A<br />2 (2) (4) A B B<br />3 (2) (5) B B B<br />4 (4) (6) B A A B<br />5 (7) (7) A B A A B A<br />6 (3) (8) A B A C C<br />第 18 页<br />4<br />作。对LZW算法进一步的改进是增加可变的码字长度，以及在词典中删除老的缀-符串。在GIF图像格式和UNIX<br />的压缩程序中已经采用了这些改进措施之后的LZW算法。<br />LZW算法取得了专利，专利权的所有者是美国的一个大型计算机公司—Unisys(优利系统公司)，除了商业<br />软件生产公司之外，可以免费使用LZW算法。</font>
<img src ="http://www.cppblog.com/swo2006/aggbug/11520.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-21 14:20 <a href="http://www.cppblog.com/swo2006/articles/11520.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>gzip</title><link>http://www.cppblog.com/swo2006/articles/11515.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Mon, 21 Aug 2006 03:18:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11515.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11515.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11515.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11515.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11515.html</trackback:ping><description><![CDATA[
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">（注：以下关于技术细节的描述是以<span lang="EN-US"> gzip </span>的公开源代码为基础的，如果需要完整的代码，可以在<span lang="EN-US"> gzip </span>的官方网站 <span lang="EN-US"><a href="http://www.gzip.org/" target="_blank">www.gzip.org</a></span>下载。下面提到的每一个问题，都首先介绍最直观简单的解决方法，然后指出这种方法的弊端所在，最后介绍<span lang="EN-US"> gzip </span>采用的做法，这样也许能使读者对<span lang="EN-US"> gzip </span>看似复杂、不直观的做法的意义有更好的理解。）<span lang="EN-US"><br /></span>最 直观的搜索方式是顺序搜索：以待压缩部分的第一个字节与窗口中的每一个字节依次比较，当找到一个相等的字节时，再比较后续的字节<span lang="EN-US">…… </span>遍历了窗口后得出最长匹配。<span lang="EN-US">gzip </span>用的是被称作<span lang="EN-US">“</span>哈希表<span lang="EN-US">”</span>的方法来实现较高效的搜索。<span lang="EN-US">“</span>哈希（<span lang="EN-US">hash</span>）<span lang="EN-US">”</span>是分散的意思，把待搜索的数据按照字节值分散到一个个<span lang="EN-US">“</span>桶<span lang="EN-US">”</span>中，搜索时再根据字节 值到相应的<span lang="EN-US">“</span>桶<span lang="EN-US">”</span>中去寻找。短语式压缩的最短匹配为<span lang="EN-US"> 3 </span>个字节，<span lang="EN-US">gzip </span>以<span lang="EN-US"> 3 </span>个字节的值作为哈希表的索引，但<span lang="EN-US"> 3 </span>个字节共有<span lang="EN-US"> 2 </span>的<span lang="EN-US"> 24 </span>次方种取值，需要<span lang="EN-US"> 16M </span>个桶，桶里存放的是窗口中的位置值，窗口的大小为<span lang="EN-US"> 32K</span>，所以每个桶至少要有大于两个字节的空间，哈希表将大于<span lang="EN-US"> 32M</span>，作为<span lang="EN-US"> 90 </span>年代开发的程序，这个要求是太大了，而且随着窗口的移动，哈希表里的数据会不断过时，维护这么大的表，会降低程序的效率，<span lang="EN-US">gzip </span>定义哈希表为<span lang="EN-US"> 2 </span>的<span lang="EN-US"> 15 </span>次方（<span lang="EN-US">32K</span>）个桶，并设计了一个哈希函数把<span lang="EN-US"> 16M </span>种取值对应到<span lang="EN-US"> 32K </span>个桶中，不同的值被对应到相同的桶中是不可避免的，哈希函数的任务是<span lang="EN-US"><br /></span></span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">
								<span lang="EN-US">1.</span>使各种取值尽可能均匀地分布到各个桶中，避免许多不同的值集中到某些桶中，而另一些是空桶，使搜索的效率降低。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">
								<span lang="EN-US">2.</span>函数的计算尽可能地简单，因为每次<span lang="EN-US"> “</span>插入<span lang="EN-US">”</span>和<span lang="EN-US">“</span>搜寻<span lang="EN-US">”</span>哈希表都要执行哈希函数，哈希函数的复杂度直接影响程序的执行效率，容易想到的哈希函数是取<span lang="EN-US"> 3 </span>个字节的左边（或右边）<span lang="EN-US">15 </span>位二进制值，但这样只要左边（或右边）<span lang="EN-US">2 </span>个字节相同，就会被放到同一个桶中，而<span lang="EN-US"> 2 </span>个字节相同的概率是比较高的，不符合<span lang="EN-US">“</span>平均分布<span lang="EN-US">”</span>的要求。<span lang="EN-US"><br /></span></span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">
								<span lang="EN-US">gzip </span>采用的算法是：<span lang="EN-US">A(4,5) + A(6,7,8) ^ B(1,2,3) + B(4,5) + B(6,7,8) ^ C(1,2,3) +
C(4,5,6,7,8) </span>（说明：<span lang="EN-US">A </span>指<span lang="EN-US"> 3 </span>个字节中的第<span lang="EN-US"> 1 </span>个字节，<span lang="EN-US">B </span>指第<span lang="EN-US"> 2 </span>个字节，<span lang="EN-US">C </span>指第<span lang="EN-US"> 3 </span>个字节，<span lang="EN-US">A(4,5) </span>指第一个字节的第<span lang="EN-US"> 4,5 </span>位二进制码，<span lang="EN-US">“^”</span>是二进制位的异或操作，<span lang="EN-US">“+”</span>是<span lang="EN-US">“</span>连接<span lang="EN-US">”</span>而不是<span lang="EN-US">“</span>加<span lang="EN-US">”</span>，<span lang="EN-US">“^”</span>优先于<span lang="EN-US">“+”</span>）这样使<span lang="EN-US"> 3 </span>个字节都尽量<span lang="EN-US">“</span>参与<span lang="EN-US">”</span>到最后的结果中来，而且每个结果值<span lang="EN-US"> h </span>都等于<span lang="EN-US"> ((</span>前<span lang="EN-US">1</span>个<span lang="EN-US">h &lt;&lt; 5)
^ c)</span>取右<span lang="EN-US"> 15 </span>位，计算也还简单。<span lang="EN-US"><br /></span>哈希表的具体实现也值得探讨<span lang="EN-US">,</span>因为无法预先知道每一个<span lang="EN-US">“</span>桶<span lang="EN-US">”</span>会存放多少个元素，所以最简单的，会想到用链表来实现：哈希表里存放着每个桶的第一个 元素，每个元素除了存放着自身的值，还存放着一个指针，指向同一个桶中的下一个元素，可以顺着指针链来遍历该桶中的每一个元素，插入元素时，先用哈希函数
算出该放到第几个桶中，再把它挂到相应链表的最后。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">这个方案的缺点是频繁地申请和释放内存会降低运行速度；内存指针的存放占据了额外的内存开销。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">有更少内 存开销和更快速的方法来实现哈希表，并且不需要频繁的内存申请和释放：<span lang="EN-US">gzip </span>在内存中申请了两个数组，一个叫<span lang="EN-US"> head[]</span>，一个叫<span lang="EN-US"> pre[]</span>，大小都为<span lang="EN-US"> 32K</span>，根据当前位置<span lang="EN-US"> strstart </span>开始的<span lang="EN-US"> 3 </span>个字节，用哈希函数计算出在<span lang="EN-US"> head[] </span>中的位置<span lang="EN-US"> ins_h</span>，然后把<span lang="EN-US"> head[ins_h] </span>中的值记入<span lang="EN-US"> pre[strstart]</span>，再把当前位置<span lang="EN-US"> strstart </span>记入<span lang="EN-US"> head[ins_h]</span>。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">随着压缩的进行，<span lang="EN-US">head[]</span>里记载着最近的可能的匹配的位置（如果有匹配的话，<span lang="EN-US">head[ins_h]</span>不为<span lang="EN-US"> 0</span>），<span lang="EN-US">pre[]</span>中的所有位置与原始数据的位置相对应，但每一个位置保存的值是前一个最近的可能的匹配的位置。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">（<span lang="EN-US">“</span>可能的匹配<span lang="EN-US">”</span>是指哈希函数计算出的<span lang="EN-US">
ins_h </span>相同。）顺着<span lang="EN-US"> pre[] </span>中的指示找下去，直到遇到<span lang="EN-US">
0</span>，可以得到所有匹配在原始数据中的位置，<span lang="EN-US">0 </span>表示不再有更远的匹配。<span lang="EN-US"><br /></span>　　接下来很自然地要观察<span lang="EN-US"> gzip </span>具体是如何判断哈希表中数据的过时，如何清理哈希表的，因为<span lang="EN-US"> pre[] </span>里只能存放<span lang="EN-US"> 32K </span>个元素，所以这项工作是必须要做的。<span lang="EN-US"><br /></span>　 　<span lang="EN-US">gzip </span>从原始文件中读出两个窗口大小的内容（共<span lang="EN-US"> 64K
</span>字节）到一块内存中，这块内存也是一个数组，称作<span lang="EN-US"> Window[]</span>；申请<span lang="EN-US"> head[]</span>、<span lang="EN-US">pre[] </span>并清零；<span lang="EN-US">strstart
</span>置为<span lang="EN-US"> 0</span>。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">然后<span lang="EN-US"> gzip </span>边搜索边插入，搜索时通过计算<span lang="EN-US"> ins_h</span>，检查<span lang="EN-US"> head[] </span>中是否有匹配，如果有匹配，判断<span lang="EN-US"> strstart </span>减<span lang="EN-US"> head[] </span>中的位置是否大于<span lang="EN-US"> 1 </span>个窗口的大小，如果大于<span lang="EN-US"> 1 </span>个窗口的大小，就不到<span lang="EN-US"> pre[] </span>中去搜索了，因为<span lang="EN-US"> pre[] </span>中保存的位置更远了，如果不大于，就顺着<span lang="EN-US"> pre[] </span>的指示到<span lang="EN-US"> Window[] </span>中逐个匹配位置开始，逐个字节与当前位置的数据比较，以找出最长匹配，<span lang="EN-US">pre[] </span>中的位置也要判断是否超出一个窗口，如遇到超出一个窗口的位置或者<span lang="EN-US"> 0 </span>就不再找下去，找不到匹配就输出当前位置的单个字节到另外的内存（输出方法在后文中会介绍），并把<span lang="EN-US"> strstart </span>插入哈希表，<span lang="EN-US">strstart </span>递增，如果找到了匹配，就输出匹配位置和匹配长度这两个数字到另外的内存中，并把<span lang="EN-US"> strstart </span>开始的，直到<span lang="EN-US"> strstart + </span>匹配长度 为止的所有位置都插入哈希表，<span lang="EN-US">strstart += </span>匹配长度。插入哈希表的方法为：<span lang="EN-US"><br />
pre[strstart % 32K] = head[ins_h];<br />
head[ins_h] = strstart;<br /></span>可 以看出，<span lang="EN-US">pre[] </span>是循环利用的，所有的位置都在一个窗口以内，但每一个位置保存的值不一定是一个窗口以内的。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">在搜索时，<span lang="EN-US">head[] </span>和<span lang="EN-US"> pre[] </span>中的位置值对应到<span lang="EN-US"> pre[] </span>时也要<span lang="EN-US"> % 32K</span>。当<span lang="EN-US">
Window[] </span>中的原始数据将要处理完毕时，要把<span lang="EN-US"> Window[] </span>中后一窗的数据复制到前一窗，再读取<span lang="EN-US"> 32K </span>字节的数据到后一窗，<span lang="EN-US">strstart -= 32K</span>，遍历<span lang="EN-US"> head[]</span>，值小于等于<span lang="EN-US"> 32K </span>的，置为<span lang="EN-US"> 0</span>，大于<span lang="EN-US"> 32K </span>的，<span lang="EN-US">-= 32K</span>；<span lang="EN-US">pre[] </span>同<span lang="EN-US"> head[] </span>一样处理。然后同前面一样处理新一窗的数据。<span lang="EN-US"><br /></span>　　分析：现在可以 看到，虽然<span lang="EN-US"> 3 </span>个字节有<span lang="EN-US"> 16M </span>种取值，但实际上一个窗口只有<span lang="EN-US"> 32K </span>个取值需要插入哈希表，由于短语式重复的存在，实际只有<span lang="EN-US"> &lt; 32K </span>种取值插入哈希表的<span lang="EN-US"> 32K </span>个<span lang="EN-US">“</span>桶<span lang="EN-US">”</span>中，而且哈希函数又符合<span lang="EN-US">“</span>平均分布<span lang="EN-US">”</span>的要求，所以哈希表中实际存在的<span lang="EN-US">“</span>冲突<span lang="EN-US">”</span>一般不会多，对搜索效率的影响不大。可以预计，在<span lang="EN-US">“</span>一般情况<span lang="EN-US">”</span>下，每 个<span lang="EN-US">“</span>桶<span lang="EN-US">”</span>中存放的数据，正是我们要找的。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">哈希表在各种搜索算法中，实现相对的比较简单，容易理解，<span lang="EN-US">“</span>平均搜索速度<span lang="EN-US">”</span>最快，哈希函数的设计是搜索速度的关 键，只要符合<span lang="EN-US">“</span>平均分布<span lang="EN-US">”</span>和<span lang="EN-US">“</span>计算简单<span lang="EN-US">”</span>，就常常能成为诸种搜索算法中的首选，所以哈希表是最流行的一种搜索算法。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">但在某些特殊情况下，它也有缺点，比
如：<span lang="EN-US">1.</span>当键码<span lang="EN-US"> k </span>不存在时，要求找出小于<span lang="EN-US"> k </span>的最大键码或大于<span lang="EN-US"> k </span>的最小键码，哈希表无法有效率地满足这种要求。<span lang="EN-US">2.</span>哈希表的<span lang="EN-US">“</span>平均搜索速度<span lang="EN-US">”</span>是建立在概率论的基础上的，因为事先不能预知待搜索的数据集合，我们只能<span lang="EN-US">“</span>信 赖<span lang="EN-US">”</span>搜索速度的<span lang="EN-US">“</span>平均值<span lang="EN-US">”</span>，而不能<span lang="EN-US">“</span>保证<span lang="EN-US">”</span>搜索速度的<span lang="EN-US">“</span>上限<span lang="EN-US">”</span>。在同人类性命攸关的应用中（如医疗或宇航领域），将是不合适的。</span>
				</font>
		</p>
		<p class="MsoNormal" style="text-align: left; line-height: 150%;" align="left">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">这些情况及其他一些特殊情
况下，我们必须求助其他<span lang="EN-US">“</span>平均速度<span lang="EN-US">”</span>较低，但能满足相应的特殊要求的算法。（见《计算机程序设计艺术》第<span lang="EN-US">3</span>卷 排序与查找）。幸而<span lang="EN-US">“</span>在窗口中搜索匹配字节串<span lang="EN-US">”</span>不属于特殊情况。<span lang="EN-US"><o:p></o:p></span></span>
				</font>
		</p>
		<p class="MsoNormal" style="line-height: 150%;">
				<font size="5">
						<span style="font-size: 12pt; line-height: 150%; font-family: 宋体;">时间与压缩率的平衡：<span lang="EN-US"><br />
gzip </span>定义了几种可供选择的<span lang="EN-US"> level</span>，越低的<span lang="EN-US"> level
</span>压缩时间越快但压缩率越低，越高的<span lang="EN-US"> level </span>压缩时间越慢但压缩率越高。<span lang="EN-US"><br /></span>不同的<span lang="EN-US"> level </span>对下面四个变量有不同的取值：<span lang="EN-US"><br /><br />
nice_length<br />
max_chain<br />
max_lazy<br />
good_length<br /><br />
nice_length</span>： 前面说过，搜索匹配时，顺着<span lang="EN-US"> pre[] </span>的指示到<span lang="EN-US"> Window[] </span>中逐个匹配位置开始，找出最长匹配，但在这过程中，如果遇到一个匹配的长度达到或超过<span lang="EN-US"> nice_length</span>，就不再试图寻找更长的匹配。最低的 <span lang="EN-US">level </span>定义<span lang="EN-US"> nice_length </span>为<span lang="EN-US"> 8</span>，最高的<span lang="EN-US">
level </span>定义<span lang="EN-US"> nice_length </span>为<span lang="EN-US"> 258</span>（即一个字节能表示的最大短语匹配长度<span lang="EN-US"> 3 + 255</span>）。<span lang="EN-US"><br /><br />
max_chain</span>：这个值规定了顺着<span lang="EN-US"> pre[] </span>的指示往前回溯的最大次数。最低的<span lang="EN-US"> level </span>定义<span lang="EN-US"> max_chain </span>为<span lang="EN-US"> 4</span>，最高的<span lang="EN-US"> level </span>定义<span lang="EN-US">
max_chain </span>为<span lang="EN-US"> 4096</span>。当<span lang="EN-US"> max_chain </span>和<span lang="EN-US"> nice_length </span>有冲突时，以先达到的为准。</span>
				</font>
		</p>
<img src ="http://www.cppblog.com/swo2006/aggbug/11515.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-21 11:18 <a href="http://www.cppblog.com/swo2006/articles/11515.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>gzip原理与实现</title><link>http://www.cppblog.com/swo2006/articles/11453.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Sat, 19 Aug 2006 06:54:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11453.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11453.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11453.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11453.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11453.html</trackback:ping><description><![CDATA[
		<br />
		<div align="center">
				<center>
						<br />
				</center>
		</div>
		<div align="center">
				<table class="content" border="0" cellpadding="0" cellspacing="0" height="200" width="96%">
						<tbody>
								<tr>
										<td height="200" valign="top" width="100%">
												<p>   
gzip
使用deflate算法进行压缩。zlib，以及图形格式png，使用的压缩算法也是deflate算法。从gzip的源码中，我们了解到了
defalte算法的原理和实现。我阅读的gzip版本为
gzip-1.2.4。下面我们将要对deflate算法做一个分析和说明。首先简单介绍一下基本原理，然后详细的介绍实现。<br /><br /><b>1 gzip 所使用压缩算法的基本原理</b><br /><br />gzip
对于要压缩的文件，首先使用LZ77算法的一个变种进行压缩，对得到的结果再使用Huffman编码的方法（实际上gzip根据情况，选择使用静态
Huffman编码或者动态Huffman编码，详细内容在实现中说明）进行压缩。所以明白了LZ77算法和Huffman编码的压缩原理，也就明白了
gzip的压缩原理。我们来对LZ77算法和Huffman编码做一个简单介绍。<br /><br />1.1 LZ77算法简介<br /><br />这一算法是由Jacob Ziv 和 Abraham Lempel 于 1977 年提出，所以命名为 LZ77。<br /><br />1.1.1 LZ77算法的压缩原理<br /><br />如
果文件中有两块内容相同的话，那么只要知道前一块的位置和大小，我们就可以确定后一块的内容。所以我们可以用（两者之间的距离，相同内容的长度）这样一对
信息，来替换后一块内容。由于（两者之间的距离，相同内容的长度）这一对信息的大小，小于被替换内容的大小，所以文件得到了压缩。<br /><br />下面我们来举一个例子。<br /><br />有一个文件的内容如下<br />http://jiurl.yeah.net http://jiurl.nease.net<br /><br />其中有些部分的内容，前面已经出现过了，下面用()括起来的部分就是相同的部分。<br />http://jiurl.yeah.net (http://jiurl.)nease(.net)<br /><br />我们使用 (两者之间的距离，相同内容的长度) 这样一对信息，来替换后一块内容。<br />http://jiurl.yeah.net (22,13)nease(23,4)<br /><br />(22,13)中，22为相同内容块与当前位置之间的距离，13为相同内容的长度。<br />(23,4)中，23为相同内容块与当前位置之间的距离，4为相同内容的长度。<br />由于（两者之间的距离，相同内容的长度）这一对信息的大小，小于被替换内容的大小，所以文件得到了压缩。<br /><br />1.1.2 LZ77使用滑动窗口寻找匹配串<br /><br />LZ77算法使用"滑动窗口"的方法，来寻找文件中的相同部分，也就是匹配串。我们先对这里的串做一个说明，它是指一个任意字节的序列，而不仅仅是可以在文本文件中显示出来的那些字节的序列。这里的串强调的是它在文件中的位置，它的长度随着匹配的情况而变化。<br /><br />LZ77
从文件的开始处开始，一个字节一个字节的向后进行处理。一个固定大小的窗口（在当前处理字节之前，并且紧挨着当前处理字节），随着处理的字节不断的向后滑
动，就象在阳光下，飞机的影子滑过大地一样。对于文件中的每个字节，用当前处理字节开始的串，和窗口中的每个串进行匹配，寻找最长的匹配串。窗口中的每个
串指，窗口中每个字节开始的串。如果当前处理字节开始的串在窗口中有匹配串，就用(之间的距离，匹配长度)
这样一对信息，来替换当前串，然后从刚才处理完的串之后的下一个字节，继续处理。如果当前处理字节开始的串在窗口中没有匹配串，就不做改动的输出当前处理
字节。<br /><br />处理文件中第一个字节的时候，窗口在当前处理字节之前，也就是还没有滑到文件上，这时窗口中没有任何内容，被处理的字节就会不做改动的输出。随着处理的不断向后，窗口越来越多的滑入文件，最后整个窗口滑入文件，然后整个窗口在文件上向后滑动，直到整个文件结束。<br /><br />1.1.3 使用LZ77算法进行压缩和解压缩<br /><br />为
了在解压缩时，可以区分“没有匹配的字节”和“（之间的距离，匹配长度）对”，我们还需要在每个“没有匹配的字节”或者“（之间的距离，匹配长度）对”之
前，放上一位，来指明是“没有匹配的字节”，还是“（之间的距离，匹配长度）对”。我们用0表示“没有匹配的字节”，用1表示“（之间的距离，匹配长度）
对”。<br /><br />实际中，我们将固定（之间的距离，匹配长度）对中的，“之间的距离”和“匹配长度”所使用的位数。由于我们要固定“之间的距离”所
使用的位数，所以我们才使用了固定大小的窗口，比如窗口的大小为32KB，那么用15位（2^15=32K）就可以保存0-32K范围的任何一个值。实际
中，我们还将限定最大的匹配长度，这样一来，“匹配长度”所使用的位数也就固定了。<br /><br />实际中，我们还将设定一个最小匹配长度，只有当两个串
的匹配长度大于最小匹配长度时，我们才认为是一个匹配。我们举一个例子来说明这样做的原因。比如，“距离”使用15位，“长度”使用8位，那么“（之间的
距离，匹配长度）对”将使用23位，也就是差1位3个字节。如果匹配长度小于3个字节的话，那么用“（之间的距离，匹配长度）对”进行替换的话，不但没有
压缩，反而会增大，所以需要一个最小匹配长度。<br /><br />压缩：<br /><br />从文件的开始到文件结束，一个字节一个字节的向后进行处理。用当前
处理字节开始的串，和滑动窗口中的每个串进行匹配，寻找最长的匹配串。如果当前处理字节开始的串在窗口中有匹配串，就先输出一个标志位，表明下面是一个
(之间的距离，匹配长度) 对，然后输出(之间的距离，匹配长度)
对，然后从刚才处理完的串之后的下一个字节，继续处理。如果当前处理字节开始的串在窗口中没有匹配串，就先输出一个标志位，表明下面是一个没有改动的字
节，然后不做改动的输出当前处理字节，然后继续处理当前处理字节的下一个字节。<br /><br />解压缩：<br /><br />从文件开始到文件结束，每次先读
一位标志位，通过这个标志位来判断下面是一个(之间的距离，匹配长度)
对，还是一个没有改动的字节。如果是一个（之间的距离，匹配长度）对，就读出固定位数的（之间的距离，匹配长度）对，然后根据对中的信息，将匹配串输出到
当前位置。如果是一个没有改动的字节，就读出一个字节，然后输出这个字节。<br /><br />我们可以看到，LZ77压缩时需要做大量的匹配工作，而解压缩时需要做的工作很少，也就是说解压缩相对于压缩将快的多。这对于需要进行一次压缩，多次解压缩的情况，是一个巨大的优点。<br /><br /><br />1.2 Huffman编码简介<br /><br />1.2.1 Huffman编码的压缩原理<br /><br />我
们把文件中一定位长的值看作是符号，比如把8位长的256种值，也就是字节的256种值看作是符号。我们根据这些符号在文件中出现的频率，对这些符号重新
编码。对于出现次数非常多的，我们用较少的位来表示，对于出现次数非常少的，我们用较多的位来表示。这样一来，文件的一些部分位数变少了，一些部分位数变
多了，由于变小的部分比变大的部分多，所以整个文件的大小还是会减小，所以文件得到了压缩。<br /><br />1.2.2 Huffman编码使用Huffman树来产生编码<br /><br />要
进行Huffman编码，首先要把整个文件读一遍，在读的过程中，统计每个符号（我们把字节的256种值看作是256种符号）的出现次数。然后根据符号的
出现次数，建立Huffman树，通过Huffman树得到每个符号的新的编码。对于文件中出现次数较多的符号，它的Huffman编码的位数比较少。对
于文件中出现次数较少的符号，它的Huffman编码的位数比较多。然后把文件中的每个字节替换成他们新的编码。<br /><br />建立Huffman树：<br /><br />把所有符号看成是一个结点，并且该结点的值为它的出现次数。进一步把这些结点看成是只有一个结点的树。<br /><br />每次从所有树中找出值最小的两个树，为这两个树建立一个父结点，然后这两个树和它们的父结点组成一个新的树，这个新的树的值为它的两个子树的值的和。如此往复，直到最后所有的树变成了一棵树。我们就得到了一棵Huffman树。<br /><br />通过Huffman树得到Huffman编码：<br /><br />这棵Huffman树，是一棵二叉树，它的所有叶子结点就是所有的符号，它的中间结点是在产生Huffman树的过程中不断建立的。<br /><br />我们在Huffman树的所有父结点到它的左子结点的路径上标上0，右子结点的路径上标上1。<br /><br />现在我们从根节点开始，到所有叶子结点的路径，就是一个0和1的序列。我们用根结点到一个叶子结点路径上的0和1的序列，作为这个叶子结点的Huffman编码。<br /><br /><br />我们来看一个例子。<br /><br />有一个文件的内容如下<br />abbbbccccddde<br /><br />我们统计一下各个符号的出现次数，<br /><br />a b c d e<br />1 4 4 3 1<br /><br />建立Huffman树的过程如下图所示 </p>
												<p align="center">
														<img alt="" src="http://dev.csdn.net/Develop/ArticleImages/25/25628/CSDN_Dev_Image_2004-3-151005380.gif" border="0" height="1280" width="480" />
												</p>
												<p>
														<br />通过最终的Huffman树，我们可以得到每个符号的Huffman编码。<br /><br />a 为 110<br />b 为 00<br />c 为 01<br />d 为 10<br />e 为 111<br /><br />我们可以看到，Huffman树的建立方法就保证了，出现次数多的符号，得到的Huffman编码位数少，出现次数少的符号，得到的Huffman编码位数多。<br /><br />各个符号的Huffman编码的长度不一，也就是变长编码。对于变长编码，可能会遇到一个问题，就是重新编码的文件中可能会无法如区分这些编码。<br />比如，a的编码为000，b的编码为0001，c的编码为1，那么当遇到0001时，就不知道0001代表ac，还是代表b。出现这种问题的原因是a的编码是b的编码的前缀。<br />由于Huffman编码为根结点到叶子结点路径上的0和1的序列，而一个叶子结点的路径不可能是另一个叶子结点路径的前缀，所以一个Huffman编码不可能为另一个Huffman编码的前缀，这就保证了Huffman编码是可以区分的。<br /><br />1.2.3 使用Huffman编码进行压缩和解压缩<br /><br />为了在解压缩的时候，得到压缩时所使用的Huffman树，我们需要在压缩文件中，保存树的信息，也就是保存每个符号的出现次数的信息。<br /><br />压缩：<br /><br />读文件，统计每个符号的出现次数。根据每个符号的出现次数，建立Huffman树，得到每个符号的Huffman编码。将每个符号的出现次数的信息保存在压缩文件中，将文件中的每个符号替换成它的Huffman编码，并输出。<br /><br />解压缩：<br /><br />得到保存在压缩文件中的，每个符号的出现次数的信息。根据每个符号的出现次数，建立Huffman树，得到每个符号的Huffman编码。将压缩文件中的每个Huffman编码替换成它对应的符号，并输出。<br /><br /><b>2 gzip 所使用压缩算法的实现</b><br /><br />我们将gzip的实现分成很多个部分，一个个来说明，这样做的原因见本文最后一部分。<br />gzip 中所使用的各种实现技巧的出处或者灵感，gzip 的作者在源码的注释中进行了说明。<br /><br />2.1 寻找匹配串的实现<br /><br />为一个串寻找匹配串需要进行大量的匹配工作，而且我们还需要为很多很多个串寻找匹配串。所以 gzip 在寻找匹配串的实现中使用哈希表来提高速度。<br /><br />要达到的目标是，对于当前串，我们要在它之前的窗口中，寻找每一个匹配长度达到最小匹配的串，并找出匹配长度最长的串。<br /><br />在 gzip 中，最小匹配长度为3，也就是说，两个串，最少要前3个字节相同，才能算作匹配。为什么最小匹配长度为3，将在后面说明。<br /><br />gzip 对遇到的每一个串，首先会把它插入到一个“字典”中。这样当以后有和它匹配的串，可以直接从“字典”中查出这个串。<br /><br />插
入不是乱插，查也不是乱查。插入的时候，使用这个插入串的前三个字节，计算出插入的“字典”位置，然后把插入串的开始位置保存在这个“字典”位置中。查出
的时候，使用查出串的前三个字节，计算出“字典”位置，由于插入和查出使用的是同一种计算方法，所以如果两个串的前三个字节相同的话，计算出的“字典”位
置肯定是相同的，所以就可以直接在该“字典”位置中，取出以前插入时，保存进去的那个串的开始位置。于是查出串，就找到了一个串，而这个串的前三个字节和
自己的一样（其实只是有极大的可能是一样的，原因后面说明），所以就找到了一个匹配串。<br /><br />如果有多个串，他们的前三个字节都相同，那么他们的“字典”位置，也都是相同的，他们将被链成一条链，放在那个“字典”位置上。所以，如果一个串，查到了一个“字典”位置，也就查到了一个链，所有和它前三个字节相同的串，都在这个链上。<br /><br />也
就是说，当前串之前的所有匹配串被链在了一个链上，放在某个“字典”位置上。而当前串使用它的前三个字节，进行某种计算，就可以得到这个“字典”位置（得
到了“字典”位置之后，它首先也把自己链入到这个链上），也就找到了链有它的所有匹配串的链，所以要找最长的匹配，也就是遍历这个链上的每一个串，看和哪
个串的匹配长度最大。<br /><br />下面我们更具体的说明，寻找匹配串的实现。<br /><br />我们前面所说的“字典”，是一个数组，叫做head[]（为什么叫head,后面进行说明）。<br />我们前面所说的“字典”位置，放在一个叫做ins_h的变量中。<br />我们前面所说的链，是在一个叫做prev[]的数组中。<br /><br />插入：<br /><br />当前字节为第 strstart 个字节。通过第strstart,strstart+1,strstart+2,这三个字节，使用一个设计好的哈希函数算出ins_h，也就是插入的位置。然后将当前字节的位置，即strstart，保存在head[ins_h]中。<br />注意由 strstart,strstart+1,strstart+2,这三个字节（也就是strstart开始处的串的头三个字节，也就是当前字节和之后的两个字节）确定了ins_h。head[ins_h]中保存的又是strstart，也就是这个串开始的位置。<br /><br />判断是否有匹配：<br /><br />当
前串的前三个字节，使用哈希函数算出ins_h，这时如果head[ins_h]的值不为空的话，那么head[ins_h]中的值，便是之前保存在这里
的另一个串的位置，并且这个串的前三个字节算出的ins_h，和当前串的前三个字节算出的ins_h相同。也就是说有可能有匹配。如果head
[ins_h]的值为空的话，那么肯定没有匹配。<br /><br />gzip所使用的哈希函数：<br /><br />gzip 所使用的哈希函数，用三个字节来计算一个ins_h，这是由于最小匹配为三个字节。<br /><br />对于相同的三个字节，通过哈希函数得到的ins_h必然是相同的。<br />而不同的三个字节，通过哈希函数有可能得到同一个ins_h，不过这并不要紧，<br />当gzip发现head[ins_h]不空后，也就是说有可能有匹配串的话，会对链上的每一个串进行真正的串的比较。<br /><br />所以一个链上的串，只是前三个字节用哈希函数算出的值相同，而并不一定前三个字节都是相同的。但是这样已经很大的缩小了需要进行串比较的范围。<br /><br />我们来强调一下，前三个字节相同的串，必然在同一个链上。在同一个链上的，不一定前三个字节都相同。<br /><br />不
同的三个字节有可能得到同一个结果的原因是，三个字节，一共24位，有2^24种可能值。而三个字节的哈希函数的计算结果为15位，有2^15种可能值。
也就是说2^24种值，与2^15种值进行对应，必然是多对一的，也就是说，必然是有多种三个字节的值，用这个哈希函数计算出的值都是相同的。<br /><br />而我们使用哈希函数的理由是，实际上，我们只是在一个窗口大小的范围内（后面将会看到）寻找匹配串，一个窗口的大小范围是很有限的，能出现的三个字节的值组合情况也是很有限的，将远远小于2^24，使用合适的哈希函数是高效的。<br /><br />前三个字节相同的所有的串所在的链：<br /><br />head[ins_h]
中的值，有两个作用。一个作用，是一个前三个字节计算结果为ins_h的串的位置。另一个作用，是一个在prev[]数组中的索引，用这个索引在prev
[]中，将找到前一个前三个字节计算结果为ins_h的串的位置。即prev[head[ins_h]]的值（不为空的话）为前一个前三个字节计算结果为
ins_h的串的位置。<br /><br />prev[]的值，也有两个作用。一个作用，是一个前三个字节计算结果为ins_h的串的位置。另一个作用，是一
个在prev[]数组中的索引，用这个索引在prev[]中，将找到前一个前三个字节计算结果为ins_h的串的位子哈。即prev[]的值（不为空的
话）为前一个三个字节计算结果为ins_h的串的位置。<br /><br />直到prev[]为空，表示链结束。<br /><br />我们来举一个例子，串，<br />0abcd abce,abcf_abcg<br /><br />当处理到abcg的a时，由abcg的abc算出ins_h。<br />这时的head[ins_h]中为 11，即串"abcf abcg"的开始位置。<br />这时的prev[11]中为 6，即串"abce abcf abcg"的开始位置。<br />这时的prev[6]中为 1，即串"abcd abce abcf abcg"的开始位置。<br />这时的prev[1]中为 0。表示链结束了。<br /><br />我们看到所有头三个字母为abc的串，被链在了一起，从head可以一直找下去，直到找到0。<br /><br />链的建立：<br /><br />gzip
在每次处理当前串的时候，首先用当前串的前三个字节计算出ins_h，然后，就要把当前的串也插入到相应的链中，也就是把当前的串的位置，保存到
head[ins_h] 中，而此时，head[ins_h]
中（不空的话）为前一个串的开始位置。所以这时候需要把前一个串的位置，也就是原来的head[ins_h]放入链中。于是把现在的head
[ins_h]的值，用当前串的位置做索引，保存到 prev[] 中。然后再把 head[ins_h] 赋值为当前串的位置。<br /><br />如果当前串的位置为strstart的话，那么也就是<br />prev[strstart] = head[ins_h];<br />head[ins_h] = strstart;<br /><br />就这样，每次把一个串的位置加入到链中，链就形成了。<br /><br />现在我们也就知道了，前三个字节计算得到同一ins_h的所有的串被链在了一起，head[ins_h]为链头，prev[]数组中放着的更早的串的位置。head数组和prev数组的名字，也正反应了他们的作用。<br /><br />链的特点：<br /><br />越向前（prev）与当前处理位置之间的距离越大。比如，当前处理串，算出了ins_h，而且head[ins_h]中的值不空，那么head[ins_h]就是离当前处理串距离最近的一个可能的匹配串，并且顺着prev[]向前所找到的串，越来距离越远。<br /><br />匹配串中的字节开始的串的插入：<br /><br />我们说过了，所有字节开始的串，都将被插入“字典”。对于确定了的匹配串，匹配串中的每个字节开始的串，仍要被插入“字典”，以便后面串可以和他们进行匹配。<br /><br />注意：<br /><br />对
于文件中的第0字节，情况很特殊，它开始的串的位置为0。所以第0串的前三个字节计算出ins_h之后，在head[ins_h]中保存的位置为0。而对
是否有可能有匹配的判断，就是通过head[ins_h]不为0，并且head[ins_h]的值为一个串的开始位置。所以第0字节开始的串，由于其特殊
性，将不会被用来匹配，不过这种情况只会出现在第0个字节，所以通常不会造成影响，即使影响，也会极小。<br /><br />例如，文件内容为<br /><br />jiurl jiurl<br /><br />找到的匹配情况如下，[]所括部分。<br /><br />jiurl j[iurl]<br /><br />2.2 懒惰啊匹配（lazy match）<br /><br />对
于当前字节开始的串，寻找到了最长匹配之后，gzip并不立即决定使用这个串进行替换。而是看看这个匹配长度是否满意，如果匹配长度不满意，而下一个字节
开始的串也有匹配串的话，那么gzip就找到下一个字节开始的串的最长匹配，看看是不是比现在这个长。这叫懒惰啊匹配。如果比现在这个长的话，将不使用现
在的这个匹配。如果比现在这个短的话，将确定使用现在的这个匹配。<br /><br />我们来举个例子，串<br /><br />0abc bcde abcde<br /><br />处理到第10字节时，也就是"abcde"的a时，找到最长匹配的情况如下，[]所括部分。<br /><br />0abc bcde [abc]de<br /><br />这时，再看看下一个字节，也就是第11字节的情况，也就是'abcde"的b，找到最长匹配的情况如下，[]所括部分。<br /><br />0abc bcde a[bcde]<br /><br />发现第二次匹配的匹配长度大，就不使用第一次的匹配串。我们也看到了如果使用第一次匹配的话，将错过更长的匹配串。<br /><br />在满足懒惰啊匹配的前提条件下，懒惰啊匹配不限制次数，一次懒惰啊匹配发现了更长的匹配串之后，仍会再进行懒惰啊匹配，如果这次懒匹配，发现了更长的匹配串，那么上一次的懒匹配找到的匹配串就不用了。<br /><br />进
行懒惰啊匹配是有条件的。进行懒惰啊匹配必须满足两个条件，第一，下一个处理字节开始的串，要有匹配串，如果下一个处理字节开始的串没有匹配串的话，那么
就确定使用当前的匹配串，不进行懒匹配。第二，当前匹配串的匹配长度，gzip不满意，也就是当前匹配长度小于max_lazy_match
（max_lazy_match在固定的压缩级别下，有固定的值）。<br /><br />讨论：<br /><br />我们可以看到了做另外一次尝试的原因。如果当前串有匹配就使用了的话，可能错过更长匹配的机会。使用懒惰啊匹配会有所改善。<br />不过从我简单的分析来看，使用懒惰啊匹配对压缩率的改善似乎是非常有限的。<br /><br />2.3 大于64KB的文件，窗口的实现<br /><br />窗口的实现：<br /><br />实际中，当前串（当前处理字节开始的串）只是在它之前的窗口中寻找匹配串的，也就是说只是在它之前的一定大小的范围内寻找匹配串的。有这个限制的原因，将在后面说明。<br /><br />gzip 的窗口大小为 WSIZE，32KB。<br /><br />内存中有一个叫window[]的缓冲区，大小为2个窗口的大小，也就是64KB。文件的内容将被读到这个window[]中，我们在window[]上进行LZ77部分的处理，得到结果将放在其他缓冲区中。<br /><br />gzip
对window[]中的内容，从开始处开始，一个字节一个字节的向后处理。有一个指针叫strstart（其实是个索引），指向当前处理字节，当当前处理
字节开始的串没有匹配时，不做改动的输出当前处理字节，strstart向后移动一个字节。当当前处理字节开始的串找到了匹配时，输出（匹配长度，相隔距
离）对，strstart向后移动匹配长度个字节。我们把strstart到window[]结束的这部分内容，叫做 lookahead
buffer，超前查看缓冲区。这样叫的原因是，在我们处理当前字节的时候，就需要读出之后的字节来进行串的匹配。在一个变量lookahead中，保存
着超前查看缓冲区所剩的字节数。lookahead，最开始被初始化为整个读入内容的大小，随着处理的进行，strstart不断后移，超前查看缓冲区不
断减小，lookahead的值也不断的减小。<br /><br />我们需要限制查找匹配串的范围为一个窗口的大小（这么做的原因后面说明），也就是说，只能
在当前处理字节之前的32KB的范围内寻找匹配串。而，由于处理是在2个窗口大小，也就是64KB大小的缓冲区中进行的，所以匹配链上的串与当前串之间的
距离是很有可能超过32KB的。那么gzip是如何来实现这个限制的呢？<br /><br />gzip
通过匹配时的判断条件来实现这个限制。当当前串计算ins_h，发现head[ins_h]值不为空时（head[ins_h]为一个串的开始位置），说
明当前串有可能有匹配串，把这个值保存在 hash_head中。这时就要做一个限制范围的判断，strstart - hash_head
&lt;= 窗口大小，strstart-hash_head
是当前串和最近的匹配串之间的距离，（注意前面说过，链头和当前串的距离最近，越向前（prev）与当前处理位置之间的距离越大），也就是说要判断当前串
和距离最近的匹配串之间的距离是否在一个窗口的范围之内。如果不是的话，那么链上的其他串肯定更远，肯定更不在一个窗口的范围之内，就不进行匹配处理了。
如果是在一个窗口的范围之内的话，还需要在链上寻找最长的匹配串，在和每个串进行比较的时候，也需要判断当前串和该串的距离是否超过一个窗口的范围，超过
的话，就不能进行匹配。<br /><br />实际中，gzip为了使代码简单点，距离限制要比一个窗口的大小还要小一点。<br /><br />小于64KB的文件：<br /><br />初始化的时候，会首先从文件中读64KB的内容到window[]中。<br /><br />对于小于64KB的文件，整个文件都被读入到window[]中。在window[]上进行LZ77的处理，从开始直到文件结束。<br /><br />大于64KB的文件：<br /><br />每
处理一个字节都要判断 lookahead &lt; MIN_LOOKAHEAD
，也就是window中还没有处理的字节是否还够MIN_LOOKAHEAD ，如果不够的话，就会导致
fill_window()，从文件中读内容到window[]中。由于我们一次最大可能使用的超前查看缓冲区的大小为，最大匹配长度（258个字节，后
面进行说明）加上最小匹配长度，也就是下一个处理字节开始的串，可以找到一个最大匹配长度的匹配，发生匹配之后，还要预读一个最小匹配长度来计算之后的
ins_h。<br /><br />不管是大于64KB的文件，还是小于64KB的文件，随着处理的进行，最终都要到文件的结束，在接近文件结束的时候，都会出
现 lookahead &lt; MIN_LOOKAHEAD ，对于这种情况，fill_window()
读文件，就再读不出文件内容了，于是fill_window()会设置一个标志eofile，表示文件就要结束了，之后肯定会接着遇到
lookahead &lt; MIN_LOOKAHEAD ，不过由于设置了 eofile 标志，就不会再去试图读文件到window[]中了。<br /><br />压缩开始之前的初始化，会从文件中读入64KB的内容到window[]中，窗口大小为32KB，也就是读入2窗的内容到window[]中。我们把第一窗的内容叫做w1_32k，第二窗的内容叫做w2_32k。<br /><br />压
缩不断进行，直到 lookahead &lt;
MIN_LOOKAHEAD，也就是处理到了64KB内容的接近结束部分，也就是如果再处理，超前查看缓冲区中的内容就可能不够了。由于
lookahead &lt; MIN_LOOKAHEAD ，将执行 fill_window()。<br /><br />fill_window() 判断是否压缩已经进行到了2窗内容快用完了，该把新的内容放进来了。如果是的话，<br /><br />fill_window() 把第二窗的内容 w2_32k，复制到第一窗中，第一窗中的内容就被覆盖掉了，然后对match_start,strstart之类的索引，做修正。<br />然后更新匹配链的链头数组，head[]，从头到尾过一遍，如果这个头中保存的串的位置，在w2_32k中，就对这个串的位置做修正。<br />如果这个头中保存的串的位置，在w1_32k中，就不要了，设为空，因为第一窗的内容我们已经覆盖掉了。<br />然后更新prev[]数组，从头到尾过一遍，如果某项的内容，在w2_32k中，就做修正。如果这项的内容，在w1_32k中，就不要了，设为空，因为第一窗的内容我们已经覆盖掉了。<br /><br />最后fill_window()从文件中再读出一窗内容，也就是读出32KB的内容，复制到第二个窗中，注意第二个窗口中原来的内容，已经被复制到了第一个窗口中。<br /><br />就这样，一窗窗的处理，直到整个文件结束。<br /><br />分析：<br /><br />到
第二窗文件内容也快要处理完的时候，才会从文件中读入新的内容。而这时，第一窗中的所有串，对于当前处理字节和之后的字节来说，已经超出了一个窗口的距
离，当前处理字节和之后的字节不能和第一窗的串进行匹配了，也就是说第一窗的内容已经没有用了。所有插入字典的第一窗的串也已经没有用了。所以覆盖第一窗
的内容是合理的，将字典中第一窗的串的开始位置都设为空也是合理的。<br /><br />将第二窗的内容复制到第一窗中，那么第二窗在字典中的所有索引都需要做相应的修正。<br /><br />由
于第二窗的内容已经复制到了第一窗中，所以我们可以将新的内容读入到第二窗中，新的内容之前的32KB的内容，就是原来的第二窗中的内容。而这时，做过修
正的字典中，仍然有原来第二窗中所有串的信息，也就是说，新的内容，可以继续利用前面一个窗口大小的范围之内的串，进行压缩，这也是合理的。<br /><br />2.4 其他问题1<br /><br />现
在来说明一下，为什么最小匹配长度为3个字节。这是由于，gzip
中，(匹配长度，相隔距离)对中，"匹配长度"的范围为3-258，也就是256种可能值，需要8bit来保存。"相隔距离"的范围为0-32K，需要
15bit来保存。所以一个(匹配长度，相隔距离)对需要23位，差一位3个字节。如果匹配串小于3个字节的话，使用(匹配长度，相隔距离)对进行替换，
不但没有压缩，反而还会增大。所以保存(匹配长度，相隔距离)对所需要的位数，决定了最小匹配长度至少要为3个字节。<br /><br />最大匹配长度为258的原因是，综合各种因素，决定用8位来保存匹配长度，8位的最大值为255。实际中，我们在(匹配长度，相隔距离)对中的“匹配长度”保存的是，实际匹配长度-最小匹配长度，所以255对应的实际匹配长度为258。<br /><br />在进行匹配时，会对匹配长度进行判断，保证到达最大匹配长度时，匹配就停止。也就是说，即使有两个串的相同部分超过了最大匹配长度，也只匹配到最大匹配长度。<br /><br />保存相隔距离所用的位数和窗口大小是互相决定的，综合两方面各种因素，确定了窗口大小，也就确定了保存相隔距离所使用的位数。<br /><br />2.5 gzip 的 LZ77部分的实现要点<br /><br />gzip 的 LZ77 部分的实现主要在函数 defalte() 中。<br /><br />所使用的缓冲区<br /><br />window[] 用来放文件中读入的内容。<br /><br />l_buf[]，d_buf[]，flag_buf[] 用来放LZ77压缩得到的结果。<br />l_buf[] 中的每个字节是一个没有匹配的字节，或者是一个匹配的对中的匹配长度-3。l_buf[]共用了inbuf[]。<br />d_buf[] 中的每个unsigned short，是一个匹配的对中的相隔距离。<br />flag_buf[] 中每位是一个标志，用来指示l_buf[]中相应字节是没有匹配的字节，还是一个匹配的对中的匹配长度-3。<br /><br />prev[]，head[] 用来存放字典信息。实际上 head 为宏定义 prev+WSIZE。<br /><br />初始化过程中，调用 lm_init()。<br />lm_init() 中，从输入文件中读入2个窗口大小，也就是64KB的内容到window[]中。lookahead 中为返回的读入字节数。使用window中的头两个字节，UPDATE_HASH，初始化ins_h。<br /><br />deflate()
中，一个处理循环中，首先 INSERT_STRING 把当前串插入字典，INSERT_STRING
是一个宏，作用就是用哈希函数计算当前串的ins_h，然后把原来的head[ins_h]中的内容，链入链中（放到prev中），同时把原来的head
[ins_h]保存在hash_head变量中，用来后面进行匹配判断，然后把当前串的开始位置，保存在head[ins_h]中。<br /><br />判断hash_head中保存的内容不为空，说明匹配链上有内容。调用 longest_match () 寻找匹配链上的最长匹配。<br />hash_head中保存的内容为空，说明当前字节开始的串，在窗口中没有匹配。<br />由于使用了lazy match，使得判断的情况更复杂。<br /><br />匹配串的输出，或者是没有匹配的字节的输出，都是调用函数 ct_tally()。<br />对于匹配串，输出之后，还需要为匹配串中的每个字节使用 INSERT_STRING，把匹配串中每个字节开始的串都插入到字典中。<br /><br />ct_tally
()中，把传入的"没有匹配的字节"或者是"匹配长度-3"放到l_buf[]中，然后为以后的Huffman编码做统计次数的工作，如果传入的是匹配情
况，传入的参数中会有相隔距离，把相隔距离保存在d_buf[]中。根据传入的参数，可以判断是哪种情况，然后设置一个变量中相应的标志位，每8个标志
位，也就是够一个字节，就保存到flag_buf[]中。还有一些判断，我们将在后面进行说明。<br /><br />2.6 分块输出<br /><br />LZ77 压缩的结果放在，l_buf[]，d_buf[]，flag_buf[] 中。<br />对于 LZ77 的压缩结果，可能使用一块输出或者分成多块输出（LZ77压缩一定的部分之后，就进行一次块输出，输出一块）。块的大小不固定。<br /><br />输出的时候，会对LZ77的压缩结果，进行Huffman编码，最终把Huffman编码的结果输出到outbuf[]缓冲区中。<br />进行Huffman编码，并输出的工作，在 flush_block() 中进行。<br /><br />在ct_tally()中进行判断，如果满足一些条件的话，当从ct_tally()中返回之后，就会对现有的LZ77的结果，进行Huffman编码，输出到一个块中。<br />在整个文件处理结束，deflate()函数要结束的时候，会把LZ77的结果，进行Huffman编码，输出到一个块中。<br /><br />在ct_tally()中，每当l_buf[]中的字节数（每个字节是一个没有匹配的字节或者一个匹配长度）增加0x1000，也就是4096的时候。将估算压缩的情况，以判断现在结束这个块是否比较好，如果觉得比较好，就输出一个块。如果觉得不好，就先不输出。<br /><br />而当l_buf[]满了的时候，或者d_buf[]满了的时候，将肯定对现有的LZ77压缩的结果，进行Huffman编码，输出到一个块中。<br /><br />决
定输出一块的话，会只针对这一块的内容，建立Huffman树，这一块内容将会被进行Huffman编码压缩，并被输出到outbuf[]中。如果是动态
Huffman编码，树的信息也被输出到outbuf[]中。输出之后，会调用init_block()，初始化一个新块，重新初始化一些变量，包括动态
树的结点被置0，也就是说，将为新块将来的Huffman树重新开始统计信息。<br /><br />输出块的大小是不固定的，首先在进行Huffman编码之前，要输出的内容的大小就是不固定，要看情况，进行Huffman编码之后，就更不固定了。<br />块的大小不固定，那么解压缩的时候，如何区分块呢。编码树中有一个表示块结束的结点，EOB，在每次输出块的最后，输出这个结点的编码，所以解压缩的时候，当遇到了这个结点就表明一个块结束了。<br /><br />每个块最开始的2位，用来指明本块使用的是哪种编码方式，00表示直接存储，01表示静态Huffman编码，10表示动态Huffman编码。接下来的1位，指明本块是否是最后一块，0表示不是，1表示是最后一块。<br /><br />输出一个块，对现在字典中的内容没有影响，下一个块，仍将用之前形成的字典，进行匹配。<br /><br /><br />2.7 静态Huffman编码与动态Huffman编码<br /><br />静态Huffman编码就是使用gzip自己预先定义好了一套编码进行压缩，解压缩的时候也使用这套编码，这样不需要传递用来生成树的信息。<br />动态Huffman编码就是使用统计好的各个符号的出现次数，建立Huffman树，产生各个符号的Huffman编码，用这产生的Huffman编码进行压缩，这样需要传递生成树的信息。<br /><br />gzip
在为一块进行Huffman编码之前，会同时建立静态Huffman树，和动态Huffman树，然后根据要输出的内容和生成的Huffman树，计算使
用静态Huffman树编码，生成的块的大小，以及计算使用动态Huffman树编码，生成块的大小。然后进行比较，使用生成块较小的方法进行
Huffman编码。<br /><br />对于静态树来说，不需要传递用来生成树的那部分信息。动态树需要传递这个信息。而当文件比较小的时候，传递生成树的信息得不偿失，反而会使压缩文件变大。也就是说对于文件比较小的时候，就可能会出现使用静态Huffman编码比使用动态Huffman编码，生成的块小。<br /><br />2.8 编码的产生<br /><br />deflate
算法在Huffman树的基础上，又加入了几条规则，我们把这样的树称作deflate树，使得只要知道所有位长上的结点的个数，就可以得到所有结点的编
码。这样做的原因是，减少需要存放在压缩压缩文件中的用来生成树的信息。要想弄明白，deflate如何生成Huffman编码，一定要弄明白一些
Huffman树，和deflate树的性质，下面内容是对Huffman树和deflate树做了些简单研究得到的。<br /><br />Huffman树的性质<br /><br />1 叶子结点为n的话，那么整颗树的总结点为 2n-1。<br />简单证明说明，先证，最小的树，也就是只有三个结点，一个根节点，两个叶子节点的树符合。然后在任何符合的树上做最小的添加得到的树也符合。所以都符合。<br /><br />2 最左边的叶子结点的编码为0，但是位长不一定。<br /><br />deflate中增加了附加条件的huffman树的性质<br /><br />1 同样位长的叶子结点的编码值为连续的，右面的总比左面的大1。<br /><br />2 (n+1)位长最左面的叶子结点（也就是编码值最小的叶子结点）的值为n位长最右面的叶子结点（也就是编码值最大的叶子结点）的值+1，然后变长一位（也就是左移1位）。<br /><br />3 n位长的叶子结点，最右面的叶子结点（也就是编码值最大的叶子结点）的值为最左面的叶子结点（也就是编码值最小的叶子结点）的值 加上 n位长的叶子结点的个数 减 1。<br /><br />4 (n+1)位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 为 n位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 加上 n位长的叶子结点的个数，然后变长一位（也就是左移1位）。<br /><br />还有一些树的性质，比如，树的某一深度上最大可能编码数。<br /><br />从所有编码的位长，得到所有编码的编码：<br />统计每个位长上的编码个数放在bl_count[]中。<br />根据 bl_count[] 中的值，计算出每个位长上的最小编码值，放在 next_code[] 中。<br />计算方法为，code = (code + bl_count[bits-1]) &lt;&lt; 1;<br />理由是deflate二叉树的性质，(n+1)位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 为 n位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 加上 n位长的叶子结点的个数，然后变长一位（也就是左移1位）。<br /><br />然后按照代码值的顺序，为所有的代码编码。<br />编码方法为，某一位长对应的next_code[n]，最开始是这个位长上最左边的叶子结点的编码，然后++，就是下一个该位长上下一个叶子结点的编码，依次类推，直到把这个位长上的叶子结点编码完。实际上的编码为bi_reverse(next_code[])。<br />这样编码的理由是，deflate二叉树的性质。<br /><br />2.9 5棵树<br /><br />一共有5棵树 static_ltree[]，static_dtree[]，dyn_ltree[]，dyn_dtree[]，bl_tree[]。<br /><br />对于所有的树，一个叶子结点表示的符号的值为n的话，那么这个符号对应的叶子结点放在 tree[n] 中，<br />比如 static_ltree 的叶子结点'a' 的值为十进制97，那么'a'的叶子结点就放在 static_ltree[97] 。<br /><br />static_ltree[] 静态Huffman编码时，用来对没有改动的字节和匹配长度进行编码的树。<br />static_dtree[] 静态Huffman编码时，用来对相隔距离进行编码的树。<br />dyn_ltree[] 动态Huffman编码时，用来对没有改动的字节和匹配长度进行编码的树。<br />dyn_dtree[] 动态Huffman编码时，用来对相隔距离进行编码的树。<br />bl_tree[] 动态Huffman编码时，用来对解压缩时用来产生dyn_ltree[]和dyn_dtree[]的信息进行编码的树。<br /><br />静态树在初始化的时候，为每个叶子结点直接产生编码。<br />动态树，每次要输出一块的内容，就根据这一块的内容，生成动态树，再根据生成的动态树，为每个叶子结点产生编码。<br /><br />每次要输出一块的内容时，会计算用静态树编码得到的块的大小，和用动态树编码得到的块的大小，然后谁产生的块小就用谁。<br /><br />用静态编码的话，就使用 static_ltree[]，static_dtree[]，来进行编码输出。<br />用动态编码的话，就使用 dyn_ltree[]，dyn_dtree[]，bl_tree[] 来进行编码输出。<br /><br />2.10 叶子结点<br /><br />ltree （用来对没有改动的字节和匹配长度进行编码的树，静态，动态都一样）的叶子结点<br />一共 L_CODES 个，也就是286个。<br />0-255 256个叶子结点，是字节的256个值<br />256 1个叶子结点，是 END_BLOCK，用来表示块结束的叶子结点。<br />257-285 29个叶子结点，是表示匹配长度的29个范围。<br /><br />dtree （用来对相隔距离进行编码的树，静态，动态都一样）的叶子结点<br />一共 D_CODES 个，也就是30个。<br />0-29 30个叶子结点，是表示相隔距离的30个范围。<br /><br />bl_tree 的叶子结点<br />一共 BL_CODES 个，也就是19个。<br />0-15 表示编码位长为 0-15。<br />16 复制之前的编码长度3-6次。之后的两位指明重复次数。<br />17 重复编码位长为0的，3-10次，之后的3位指明重复次数。<br />18 重复编码位长为0的，11-138次，之后的7位指明重复次数。<br /><br />2.11 静态Huffman编码<br /><br />初始化base_length[],length_code[],base_dist[],dist_code[]。<br /><br />base_length[]为，29个 匹配长度 范围的，每个范围开始的长度值。<br />length_code[]为，256 个可能的匹配长度 所属的范围。<br />比如，base_length[9]=0xa，表示第9个范围的开始值为0xa。<br />length_code[11]=9,表示匹配长度为11的匹配长度，属于第9个范围。<br /><br />base_dist[],30个 匹配距离 范围的，每个范围的开始的值，就是每个范围内最小的值。<br />dist_code[],这个有点特殊，一共有32K个取值，这里把这32K种值，分成了两大类，<br />0-255这256个值为一类，这时他们直接为dist_code[]的索引。<br />256-32K为一类，这时他们的去掉低7位，和最高位，剩下的8位为索引，8位刚好索引256项。能这么做的原因是，首先最大32K的距离最大需要15位，所以16位的最高位总不会用，其次剩下这些范围的边界至少都为二进制1 000 0000 的整数倍。<br />比如 匹配距离为 10,小于256，所以它属于类 dist_code[10]=6，第6类。<br />匹
配距离为 10K ，大于256，所以它属于类
dist_code[256+10K&gt;&gt;7]=dist_code[256+10240&gt;&gt;7]=dist_code[256+80]
=dist_code[336]=0x1a=26，属于26类，26类的范围为8193-12288，10240就是在这个范围内。<br /><br />指定了每个literal的位长。（一共将有288个literal。包括256个字节值+1个EOB+29个匹配长度范围=286个。多2个是为了满树。）并统计每个位长上的literal个数放在bl_count[]中。<br /><br />根据 bl_count[] 中的值，计算出每个位长上的最小编码值，放在 next_code[] 中。<br />计算方法为，code = (code + bl_count[bits-1]) &lt;&lt; 1;<br />理由是deflate二叉树的性质，(n+1)位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 为 n位长最左面的叶子结点（也就是编码值最小的叶子结点）的值 加上 n位长的叶子结点的个数，然后变长一位（也就是左移1位）。<br /><br />然后从literal值的0，到literal值的最大。为每个literal编码。<br />编码方法为，某一位长对应的next_code[n]，最开始是这个位长上最左边的叶子结点的编码，然后++，就是下一个该位长上下一个叶子结点的编码，依次类推，直到把这个位长上的叶子结点编码完。<br />实际上的编码为bi_reverse(next_code[])。<br />比如<br />tree[n].Code = bi_reverse(next_code[len]++, len);<br /><br />此时 next_code[len] 值为 二进制 00110000 即0x30<br />tree[n].Code 最后被赋值为 二进制 00001100 即0x0c<br /><br />这样我们就得到了 static_ltree[]，它以literal的值为索引，存放着literal对应的编码。<br />比如 'a' 的值为十进制97， static_ltree[97].code=0x0089 static_ltree[97].len=8。<br />说明a的编码为二进制 10001001。<br /><br />为static_dtree
编码。这个编码很简单，由于所有结点都是5位长的（指定的），所以根据deflate二叉树性质，最左边的叶子节点编码为0，之后每次加1即可，直到编完
所有叶子结点。注意这里也要bi_reverse()一下。也就是说，编码为"从树根开始到一个叶子结点的路径对应的位流"的逆位流。<br /><br />用Huffman编码对LZ77处理结果进行编码输出。<br /><br />这时，<br />l_buf[]中每个字节为literal或者 匹配长度-MIN_MATCH。<br />d_buf[]为匹配距离，每项为16位。<br />flag_buf[]中每位为指示inbuf[]中对应字节为literal还是 匹配长度-MIN_MATCH 的标志，比如<br />flag_buf第i位为1，说明inbuf[i]为匹配长度-MIN_MATCH。<br /><br />读出flag_buf中的每一位，进行判断。<br />如果为0，表示对应的l_buf中的那个字节为literal。<br />如果为1，表示对应的l_buf中的那个字节为匹配长度-MIN_MATCH。<br /><br />对于literal，就用l_buf[]的这个值做索引，在static_ltree中得到编码，和编码长度，然后输出这个编码。<br /><br />对
于
匹配长度-MIN_MATCH，就用l_buf[]的这个值做索引，在length_code[]中首先得到这个匹配长度所在的范围，一共有29个范围。
也就是说 匹配长度-MIN_MATCH 取值范围为 (3..258)，一共有256种可能的值。这256种可能值，被分配在了29个范围中。<br /><br />我们用l_buf[]的这个值做索引，在length_code[]中得到这个匹配长度所在的范围。<br /><br />然后用 范围值+256+1 得到该范围所对应的 literal。用这个literal做索引，在static_ltree中得到编码，和编码长度，然后输出这个编码。<br /><br />然后用 范围值 做索引，在 extra_lbits[] 中得到该范围的附加位的位数，如果附加位位数不为0，<br />就输出附加位。附加位为 inbuf[]中的那个值，就是 匹配长度-MIN_MATCH 减去 这个范围的对应的 base_length[]。<br /><br />然后从d_buf[]中取出，匹配距离。<br />当匹配距离小于256时，用匹配距离做索引，在dist_code中取出对应的范围值。<br />当匹配距离不小于256时，用匹配距离右移7位，也就是用高8位，做索引，在dist_code+256中取出对应的范围值。<br /><br />对匹配距离，匹配距离的取值范围为，(1..32,768)，一共有32k种可能的值。<br />分成了30个范围。由于匹配距离的取值范围为，(1..32,768)，所以匹配距离使用15位。<br /><br />然后用距离的范围值做索引，在static_dtree[] 中得到编码，和编码长度。然后输出这个编码。<br />然后用距离的范围值做索引，在extra_dbits[] 中得到该范围的附加位的位数，如果附加位位数不为0，<br />就输出附加位。输出的附加位为 dist-base_dist[code]。<br /><br />比如，取出一个dist为10。dist_code[10]=6，说明属于第6个范围。<br />然后查 extra_dbits,extra_dbits[6]=2，说明有两个extra bits。<br />local int near extra_dbits[D_CODES] /* extra bits for each distance code */<br />= {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};<br />首先输出 static_dtree[6].len位的位流，static_dree[6].code。（static_dtree的位长都为5）<br />然后输出 extra_dbits[6]位的位流，10-base_dist[6]=10-8=2=二进制的10。<br /><br />发送完inbuf中的每个字节之后，最后发送 END_BLOCK 的编码。<br /><br />2.12 动态Huffman编码<br /><br />确定所有literal，匹配长度范围，和匹配距离范围的出现次数。<br />在进行LZ77压缩的过程中，每确定一个literal或者匹配，都会调用 ct_tally()。<br />在 ct_tally() 中，如果是一个literal，就 dyn_ltree[lc].Freq++。<br />如果是一个匹配，就 dyn_ltree[length_code[lc]+LITERALS+1].Freq++，dyn_dtree[d_code(dist)].Freq++。<br /><br />调用 build_tree() 建立 literal和匹配长度范围（也就是dyn_ltree的叶子结点，共286个） 的树，并为他们（literal和匹配长度范围）编码。<br />生成树中，heap[]是用来辅助生成树的缓冲区。<br /><br />首先把tree[]中所有出现次数不为0的值（也就是索引，比如tree[0x61]就为'a'的对应项），放到heap[]中。<br /><br />tree[] 的元素个数为 2*L_CODES+1，L_CODES为叶子结点的个数，286。<br />由Huffman二叉树性质，叶子结点为n,那么这棵树的总结点为2n-1。<br /><br />tree[] 将用来保存生成的树。tree[]的前L_CODES 项，用来存放叶子结点。比如'a'的结点信息，放在tree[0x61]中。L_CODES 之后的项用来放中间结点。<br /><br />heap[] 将用来放生成树的过程中产生的临时内容。heap[]的大小也为 2*L_CODES+1 。它的前 L_CODES 用来放<br />生成树过程中的结点，最开始是叶子结点，随着生成树的进行，两个叶子结点被弄掉，放入他们的中间结点。后 L_CODES ，从后向前用。在生成树的过程中，所有结点（根，中间，叶子）都将按权值大小顺序放在这里。<br />将来生成位长时，需要使用。<br /><br />pqdownheap(tree, SMALLEST); 的作用就是将heap中的结点中，找出freq最小的那个，放在heap[1]中。<br /><br />生成树的过程为，每次从heap中找出两个最小的结点，然后给他们弄一个父结点。并把他们的tree[]的相应内容指向他们的父结点。<br />并在heap中删掉这两个结点，而把他们的父结点加入到heap中。<br /><br />heaplen 为heap中结点的个数，每次由于要删2个结点，加1个结点，所以每次会使heaplen--，也就是结点数变少一个。<br /><br />等到heaplen，也就是结点数，小于2时，说明树已经要弄好了。<br /><br />树生成好之后，tree[]中的域 freq 和 dad 都被设置好了，调用 gen_bitlen()，为他们生成位长。<br /><br />gen_bitlen()中，<br /><br />从根开始，根的位长为0，向下，为每个结点设置位长。<br /><br />判断是否为叶子结点，判断的方法是，看是否大于最大代码，这里最大代码是286。<br /><br />当遇到叶子结点时，进行动态编码整个文件的总位长的计算，和进行静态编码整个文件的总位长的计算。<br />bl_count[bits]++; 用来一会儿产生编码。<br />由于在叶子结点的freq域中保存着这个结点的出现次数，现在又有了位长，所以可以计算该结点的动态位长。<br />而所有的结点的动态位长累加在一起就是总位长。<br />有了出现次数，对于静态，结点位长是设定好的，也同样可以进行计算。<br /><br />最后调用 gen_codes()，为所有叶子结点产生编码。和静态Huffman中的方法是相同的。<br /><br />调用 build_tree() 建立 匹配距离范围（也就是dyn_dtree的叶子结点，共30个） 的树，并为他们（匹配距离范围）编码。和生成dyn_ltree的方法是相同的。<br /><br />调用 build_bl_tree() 为l（literal&amp;匹配长度）和d（匹配距离）的位长数组 生成树，并为这些位长编码。<br /><br />调用scan_tree统计一个树中的编码长度情况。<br />分别对dyn_ltree和dyn_dtree进行统计。<br /><br />scan_tree((ct_data near *)dyn_ltree, l_desc.max_code);<br />scan_tree((ct_data near *)dyn_dtree, d_desc.max_code);<br /><br />统计结果放在 bl_tree[].Freq 中。<br /><br />弄明白了bl_tree[]中叶子结点的含义，就很容易理解scan_tree中所作的工作。<br />比如 bl_tree[0].Freq 表示编码位长为0的编码个数。<br />bl_tree[10].Freq 表示编码位长为10的编码个数。<br />bl_tree[16].Freq 表示 连续几个编码长度的出现个数都相同，这种情况的出现次数。<br /><br />最后调用 build_tree() 建立位长情况（就是那19种情况）的树，并为他们（就是那19种情况）编码。<br /><br />发送用bl_tree编码的结点位长数组。<br />defalte算法中，只要知道了一个树的每个叶子结点的位长，就可以得到该叶子结点的编码。<br />所以我们需要发送ltree的286个叶子结点的位长，我们需要发送dtree的30个叶子结点的位长。<br /><br />首先发送三个树的最大叶子结点值的一个变形。<br />send_bits(lcodes-257, 5); 发送ltree有效最大叶子结点值+1-257<br />send_bits(dcodes-1, 5); 发送dtree有效最大叶子结点值+1-1<br />send_bits(blcodes-4, 4); 发送bl_tree有效最大叶子结点值+1-4。<br />ltree最大叶子结点值，就决定了我们将要发送的ltree的叶子结点位长数组的个数。只发送到有效最大叶子结点数就行了。<br />比如，ltree有效最大叶子结点值为0x102的话，那么我们只需要发送ltree中前0x103个的位长，并告诉解压缩程序，发送了0x103个就行了。<br /><br />发送 bl_tree 的位长，注意发送的顺序是按 bl_order[] 中规定的顺序发送的。<br /><br />调用 send_tree() 先后发送 dyn_ltree,dyn_dtree 的位长。<br /><br />send_tree()中使用和scan_tree()中相同的方法，首先看这些位长属于bl_tree的19个叶子结点对应的19种情况中的哪一种，确定了是哪一种之后，<br />就按这种情况对应的叶子结点，在bl_tree中的编码，发送这个编码。直到把这些位长都发完。<br /><br />用Huffman编码对LZ77处理结果进行编码输出。和静态Huffman编码时使用的方法是相同的。<br /><br />2.13 要点<br /><br />第一，省去了LZ77用来指明是"没有改动的字节"还是"匹配的信息对"的那个标志位。<br /><br />由于gzip实现中，把匹配长度的范围和字节值，做为不同的叶子结点进行编码。比如说，值为1的字节，和一个值为1的匹配长度，他们的值虽然相同，但是他们是不同的叶子结点，他们的编码也是不同的。这样一来，解压缩时，就可以直接区分，就不必再输出那个指示位了。<br /><br />这个节省对压缩率的改善应该有不小的帮助。<br /><br />静态Huffman编码时，编码本身不会起到什么压缩作用，但是还会从这个节省中获益。<br /><br />第二，叶子结点所表示的内容。<br /><br />我们看到gzip的实现中，叶子节点所代表的内容各种各样，不仅仅是一个固定的值，而且有些代表了一个值的范围，（然后用之后的更多的位来表示这个范围中的一个值），而且还有代表情况的。<br /><br />这个实现方法是相当不错的，非常值得借鉴。<br /><br />解压缩也不说了，原因看最后。<br /><br />2.14 匹配延伸到lookahead中<br /><br />可以进行这种压缩，与解压缩，关键是解压缩的处理中，做了特别的处理。<br /><br />例，串 0aaaaa<br /><br />进行lz77压缩时，当今行到下面位置时 0a 当前位置-&gt;aaaa<br />匹配会延伸到lookahead中，结果就是 0a[匹配长度4，距离1]<br /><br />解压缩时，首先0a被做为没有改动的字节解压出来，<br />然后解压发现[匹配长度4，距离1]，<br />这里将做一个判断，看有没有延伸到lookahead中，如果有的话，将做特别的处理，一个字节一个字节的进行复制。<br /><br /><b>3 最后</b><br /><br />   
一个人，从找资料，到读资料，到读完源码，到写这个东西，花了三周多的时间，太慢了。中间到处找人希望可以一起来搞，也没找着。太慢了，太花时间了，而且
一个人，而且。反正一想起这事，就得泪水打湿了双眼，泪过三巡以后，还得把脖子伸长，头仰成一个角度，吟道:"我观古昔之英雄，慷慨然诺杯酒中。义重生轻
死知己，所以与人成大功"。哭也哭了，诗也念了，回味一下这巨感人的一套，自己把自己又感动的不行，于是再来一遍。如此这般，一遍一遍，惨不忍睹。唉，还
是该干吗干吗去吧。<br /><br /><b>参考资料：</b><br />《数据压缩技术原理与范例》<br />rfc1951 </p>
												<p>
														<br />欢迎交流，欢迎交朋友，<br />欢迎访问<br />主页 <a href="http://jiurl.yeah.net/" target="_blank"><font color="#000080">http://jiurl.yeah.net</font></a><a href="http://jiurl.nease.net/" target="_blank"><font color="#000080">http://jiurl.nease.net</font></a> 论坛 <a href="http://jiurl.cosoft.org.cn/forum" target="_blank"><font color="#000080">http://jiurl.cosoft.org.cn/forum</font></a></p>
												<p>f啊k，不带你们这样的啊，有好事不叫我。 </p>
										</td>
								</tr>
						</tbody>
				</table>
		</div>
<img src ="http://www.cppblog.com/swo2006/aggbug/11453.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-19 14:54 <a href="http://www.cppblog.com/swo2006/articles/11453.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>lzw压缩算法的c语言实现</title><link>http://www.cppblog.com/swo2006/articles/11431.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Sat, 19 Aug 2006 01:45:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11431.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11431.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11431.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11431.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11431.html</trackback:ping><description><![CDATA[ 程序由五个模块组成。<br /><br />
(1)  lzw.h      定义了一些基本的数据结构，常量，还有变量的初始化等。<br /><br />
#ifndef __LZW_H__<br />
#define __LZW_H__<br />
//------------------------------------------------------------------------------<br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;windows.h&gt;<br />
#include &lt;memory.h&gt;<br />
//------------------------------------------------------------------------------<br />
#define LZW_BASE    0x102//  The code base<br />
#define CODE_LEN       12   //  Max code length<br />
#define TABLE_LEN      4099 // It must be prime number and bigger than 2^CODE_LEN=4096. <br />
       // Such as 5051 is also ok.<br />
#define BUFFERSIZE     1024<br />
//------------------------------------------------------------------------------<br />
typedef struct<br />
{<br />
    HANDLE      h_sour;  // Source file handle.<br />
    HANDLE      h_dest;  // Destination file handle.<br />
    <br />
    HANDLE      h_suffix; // Suffix table handle.<br />
    HANDLE      h_prefix; // Prefix table handle.<br />
    HANDLE      h_code;  // Code table handle.<br />
    <br />
    LPWORD      lp_prefix; // Prefix table head pointer.<br />
    LPBYTE      lp_suffix; // Suffix table head pointer.<br />
    LPWORD      lp_code; // Code table head pointer.<br /><br />
    WORD        code;<br />
    WORD        prefix;<br />
    BYTE        suffix;<br /><br />
    BYTE        cur_code_len; // Current code length.[ used in Dynamic-Code-Length mode ]<br /><br />
}LZW_DATA,*PLZW_DATA;<br /><br /><br />
typedef struct<br />
{<br />
    WORD        top;<br />
    WORD        index;<br /><br />
    LPBYTE      lp_buffer;<br />
    HANDLE      h_buffer;<br />
    <br />
    BYTE        by_left;<br />
    DWORD       dw_buffer;<br /><br />
    BOOL        end_flag;<br /><br />
}BUFFER_DATA,*PBUFFER_DATA;<br /><br /><br />
typedef struct                  //Stack used in decode<br />
{<br />
 WORD        index;<br />
 HANDLE      h_stack;<br />
 LPBYTE      lp_stack;<br /><br />
}STACK_DATA,*PSTACK_DATA;<br />
//------------------------------------------------------------------------------<br />
VOID stack_create( PSTACK_DATA stack )<br />
{<br />
 stack-&gt;h_stack  = GlobalAlloc( GHND , TABLE_LEN*sizeof(BYTE) );<br />
 stack-&gt;lp_stack = GlobalLock( stack-&gt;h_stack );<br />
 stack-&gt;index = 0;<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID stack_destory( PSTACK_DATA stack )<br />
{<br />
 GlobalUnlock( stack-&gt;h_stack );<br />
    GlobalFree  ( stack-&gt;h_stack );<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID buffer_create( PBUFFER_DATA    buffer )<br />
{<br />
    buffer-&gt;h_buffer   = GlobalAlloc(  GHND,  BUFFERSIZE*sizeof(BYTE)  );<br />
    buffer-&gt;lp_buffer  = GlobalLock( buffer-&gt;h_buffer );<br />
    buffer-&gt;top        = 0;<br />
    buffer-&gt;index      = 0;<br />
    buffer-&gt;by_left    = 0;<br />
    buffer-&gt;dw_buffer  = 0;<br />
    buffer-&gt;end_flag   = FALSE;<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID buffer_destory( PBUFFER_DATA   buffer )<br />
{<br />
    GlobalUnlock( buffer-&gt;h_buffer );<br />
    GlobalFree  ( buffer-&gt;h_buffer );<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID re_init_lzw( PLZW_DATA lzw )    //When code table reached its top it should<br />
{                                    //be reinitialized.      <br />
    memset( lzw-&gt;lp_code, 0xFFFF, TABLE_LEN*sizeof(WORD) );<br />
    lzw-&gt;code          = LZW_BASE;<br />
    lzw-&gt;cur_code_len  = 9;<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID lzw_create(PLZW_DATA    lzw,    HANDLE h_sour,    HANDLE h_dest)<br />
{<br />
 WORD i;<br />
    lzw-&gt;h_code        = GlobalAlloc( GHND, TABLE_LEN*sizeof(WORD) );<br />
    lzw-&gt;h_prefix      = GlobalAlloc( GHND, TABLE_LEN*sizeof(WORD) );<br />
    lzw-&gt;h_suffix      = GlobalAlloc( GHND, TABLE_LEN*sizeof(BYTE) );<br />
    lzw-&gt;lp_code       = GlobalLock( lzw-&gt;h_code   );<br />
    lzw-&gt;lp_prefix     = GlobalLock( lzw-&gt;h_prefix );<br />
    lzw-&gt;lp_suffix     = GlobalLock( lzw-&gt;h_suffix );<br />
    lzw-&gt;code          = LZW_BASE;<br />
    lzw-&gt;cur_code_len  = 9;<br />
    lzw-&gt;h_sour        = h_sour;<br />
    lzw-&gt;h_dest        = h_dest;<br />
    memset( lzw-&gt;lp_code, 0xFFFF, TABLE_LEN*sizeof(WORD) );<br /><br />
}<br />
//------------------------------------------------------------------------------<br />
VOID lzw_destory(PLZW_DATA    lzw)<br />
{  <br />
    GlobalUnlock( lzw-&gt;h_code   );<br />
    GlobalUnlock( lzw-&gt;h_prefix );<br />
    GlobalUnlock( lzw-&gt;h_suffix );<br /><br />
 GlobalFree( lzw-&gt;h_code  );<br />
    GlobalFree( lzw-&gt;h_prefix );<br />
    GlobalFree( lzw-&gt;h_suffix );    <br />
}<br />
//------------------------------------------------------------------------------<br />
#endif<br /><br />
(2) fileio.h   定义了一些文件操作<br /><br />
#ifndef __FILEIO_H__<br />
#define __FILEIO_H__<br />
//------------------------------------------------------------------------------<br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;windows.h&gt;<br />
//------------------------------------------------------------------------------<br />
HANDLE  file_handle(CHAR* file_name)<br />
{<br />
    HANDLE h_file;<br />
    h_file = CreateFile(file_name,<br />
                       GENERIC_READ|GENERIC_WRITE,<br />
                       FILE_SHARE_READ|FILE_SHARE_WRITE,<br />
                       NULL,<br />
                       OPEN_ALWAYS,<br />
                       0,<br />
                       NULL<br />
                       );<br />
    return h_file;<br />
}<br />
//------------------------------------------------------------------------------<br />
WORD load_buffer(HANDLE h_sour, PBUFFER_DATA buffer)  // Load file to buffer<br />
{<br />
    DWORD ret;<br />
    ReadFile(h_sour,buffer-&gt;lp_buffer,BUFFERSIZE,&amp;ret,NULL);<br />
    buffer-&gt;index = 0;<br />
    buffer-&gt;top = (WORD)ret;<br />
    return (WORD)ret;<br />
}<br />
//------------------------------------------------------------------------------<br />
WORD empty_buffer( PLZW_DATA lzw, PBUFFER_DATA buffer)// Output buffer to file<br />
{<br />
   <br />
    DWORD ret;<br />
    if(buffer-&gt;end_flag) // The flag mark the end of decode<br />
 {<br />
  if( buffer-&gt;by_left )<br />
  {<br />
   buffer-&gt;lp_buffer[ buffer-&gt;index++ ] = (BYTE)(
buffer-&gt;dw_buffer &gt;&gt; 32-buffer-&gt;by_left
)&lt;&lt;(8-buffer-&gt;by_left);<br />
  }<br />
 }<br />
 WriteFile(lzw-&gt;h_dest, buffer-&gt;lp_buffer,buffer-&gt;index,&amp;ret,NULL);<br />
    buffer-&gt;index = 0;<br />
    buffer-&gt;top = ret;<br />
    return (WORD)ret;<br />
}<br />
//------------------------------------------------------------------------------<br />
#endif<br /><br />
(3) hash.h  定义了压缩时所用的码表操作函数，为了快速查找使用了hash算法，还有处理hash冲突的函数<br /><br />
#ifndef __HASH_H__<br />
#define __HASH_H__<br />
//------------------------------------------------------------------------------<br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;windows.h&gt;<br />
//------------------------------------------------------------------------------<br />
#define   DIV       TABLE_LEN<br />
#define   HASHSTEP  13         // It should bigger than 0.<br />
//------------------------------------------------------------------------------<br />
WORD get_hash_index( PLZW_DATA lzw )<br />
{<br />
    DWORD tmp;<br />
    WORD result;<br />
    DWORD prefix;<br />
    DWORD suffix;<br />
    prefix = lzw-&gt;prefix;<br />
    suffix = lzw-&gt;suffix;<br />
    tmp = prefix&lt;&lt;8 | suffix;<br />
    result = tmp % DIV;<br />
    return result;<br />
}<br />
//------------------------------------------------------------------------------<br />
WORD re_hash_index( WORD hash ) // If hash conflict occured we must recalculate<br />
{                               // hash index .<br />
    WORD result;<br />
    result = hash + HASHSTEP;<br />
    result = result % DIV;<br />
    return result;<br />
}<br />
//------------------------------------------------------------------------------<br />
BOOL in_table( PLZW_DATA lzw ) // To find whether current code is already in table.<br />
{<br />
    BOOL result;<br />
    WORD hash;<br /><br />
    hash = get_hash_index( lzw );<br />
    if( lzw-&gt;lp_code[ hash ] == 0xFFFF )<br />
    {<br />
        result = FALSE;    <br />
    }<br />
    else<br />
    {<br />
        if( lzw-&gt;lp_prefix[ hash ] == lzw-&gt;prefix &amp;&amp;<br />
            lzw-&gt;lp_suffix[ hash ] == lzw-&gt;suffix )<br />
        {<br />
            result = TRUE;<br />
        }<br />
        else<br />
        {<br />
            result = FALSE;<br />
            while( lzw-&gt;lp_code[ hash ] != 0xFFFF )<br />
            {<br />
                if( lzw-&gt;lp_prefix[ hash ] == lzw-&gt;prefix &amp;&amp;<br />
                    lzw-&gt;lp_suffix[ hash ] == lzw-&gt;suffix )<br />
                {<br />
                        result = TRUE;<br />
                        break;    <br />
                }<br />
                hash = re_hash_index( hash );<br />
            }<br />
        }<br />
    }<br />
    return result;<br />
}<br />
//------------------------------------------------------------------------------<br />
WORD get_code( PLZW_DATA lzw )<br />
{<br />
    WORD hash;<br />
    WORD code;<br />
    hash = get_hash_index( lzw );<br />
    if( lzw-&gt;lp_prefix[ hash ] == lzw-&gt;prefix &amp;&amp;<br />
        lzw-&gt;lp_suffix[ hash ] == lzw-&gt;suffix )<br />
    {<br />
        code = lzw-&gt;lp_code[ hash ];<br />
    }<br />
    else<br />
    {<br />
        while( lzw-&gt;lp_prefix[ hash ] != lzw-&gt;prefix ||<br />
               lzw-&gt;lp_suffix[ hash ] != lzw-&gt;suffix )<br />
        {<br />
                hash = re_hash_index( hash );    <br />
        }<br />
        code = lzw-&gt;lp_code[ hash ];<br />
    }<br />
    return code;<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID insert_table( PLZW_DATA lzw )<br />
{<br /><br />
    WORD hash;<br />
    hash = get_hash_index( lzw );<br />
    if( lzw-&gt;lp_code[ hash ] == 0xFFFF )<br />
    {<br />
        lzw-&gt;lp_prefix[ hash ] = lzw-&gt;prefix;<br />
        lzw-&gt;lp_suffix[ hash ] = lzw-&gt;suffix;<br />
        lzw-&gt;lp_code[ hash ]   = lzw-&gt;code;<br />
    }<br />
    else<br />
    {<br />
        while( lzw-&gt;lp_code[ hash ] != 0xFFFF )<br />
        {<br />
                hash = re_hash_index( hash );    <br />
        }<br />
        lzw-&gt;lp_prefix[ hash ] = lzw-&gt;prefix;<br />
        lzw-&gt;lp_suffix[ hash ] = lzw-&gt;suffix;<br />
        lzw-&gt;lp_code[ hash ]   = lzw-&gt;code;<br />
    }<br /><br />
}<br />
//------------------------------------------------------------------------------<br /><br /><br />
#endif<br /><br />
(4) encode.h  压缩程序主函数<br /><br />
#ifndef __ENCODE_H__<br />
#define __ENCODE_H__<br />
//------------------------------------------------------------------------------<br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;windows.h&gt;<br /><br />
//------------------------------------------------------------------------------<br />
VOID output_code( DWORD code ,PBUFFER_DATA out, PLZW_DATA lzw)<br />
{<br />
    out-&gt;dw_buffer |= code &lt;&lt; ( 32 - out-&gt;by_left - lzw-&gt;cur_code_len );<br />
    out-&gt;by_left += lzw-&gt;cur_code_len;<br /><br />
    while( out-&gt;by_left &gt;= 8 )<br />
    {<br />
        if( out-&gt;index == BUFFERSIZE )<br />
        {<br />
            empty_buffer( lzw,out);<br />
        }<br /><br />
        out-&gt;lp_buffer[ out-&gt;index++ ] = (BYTE)( out-&gt;dw_buffer &gt;&gt; 24 );<br />
        out-&gt;dw_buffer &lt;&lt;= 8;<br />
        out-&gt;by_left -= 8;<br />
    }<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID do_encode( PBUFFER_DATA in, PBUFFER_DATA out, PLZW_DATA lzw)<br />
{<br />
    WORD prefix;<br />
    while( in-&gt;index != in-&gt;top )<br />
    {<br />
        if( !in_table(lzw) )<br />
        {<br />
                // current code not in code table<br />
                // then add it to table and output prefix<br /><br /><br />
                insert_table(lzw);<br />
                prefix = lzw-&gt;suffix;<br />
                output_code( lzw-&gt;prefix ,out ,lzw );<br />
                lzw-&gt;code++;<br /><br />
                if( lzw-&gt;code == (WORD)1&lt;&lt; lzw-&gt;cur_code_len )<br />
                {<br />
                    // code reached current code top(1&lt;&lt;cur_code_len)<br />
                    // then current code length add one<br />
     lzw-&gt;cur_code_len++;<br />
     if( lzw-&gt;cur_code_len == CODE_LEN + 1 )<br />
     {<br />
      re_init_lzw( lzw );<br />
     }<br /><br />
                }<br />
        }<br />
        else<br />
        {<br />
                // current code already in code table <br />
                // then output nothing<br />
                prefix = get_code(lzw);<br /><br />
        }<br />
        lzw-&gt;prefix = prefix;<br />
        lzw-&gt;suffix = in-&gt;lp_buffer[ in-&gt;index++ ];<br />
    }<br />
}<br /><br />
//------------------------------------------------------------------------------<br />
VOID encode(HANDLE h_sour,HANDLE h_dest)<br />
{<br />
    LZW_DATA        lzw;<br />
    BUFFER_DATA     in ;<br />
    BUFFER_DATA     out;<br />
    <br />
    BOOL first_run = TRUE;<br /><br />
    lzw_create( &amp;lzw ,h_sour,h_dest );<br />
    buffer_create( &amp;in );<br />
    buffer_create( &amp;out );<br /><br /><br />
    while( load_buffer( h_sour, &amp;in ) )<br />
    {<br />
        if( first_run )<br />
        {// File length should be considered  but here we simply <br />
         // believe file length bigger than 2 bytes.<br />
                lzw.prefix = in.lp_buffer[ in.index++ ];<br />
                lzw.suffix = in.lp_buffer[ in.index++ ];<br />
                first_run = FALSE;<br />
        }<br />
        do_encode(&amp;in , &amp;out, &amp;lzw);<br />
    }<br />
    <br />
 output_code(lzw.prefix, &amp;out , &amp;lzw);<br />
 output_code(lzw.suffix, &amp;out , &amp;lzw);<br />
 out.end_flag = TRUE;<br />
    empty_buffer( &amp;lzw,&amp;out);<br /><br />
    lzw_destory( &amp;lzw );<br />
    buffer_destory( &amp;in );<br />
    buffer_destory( &amp;out );<br />
}<br /><br />
//------------------------------------------------------------------------------<br /><br />
#endif<br /><br />
(5) decode.h  解压函数主函数<br /><br />
#ifndef __DECODE_H__<br />
#define __DECODE_H__<br />
//------------------------------------------------------------------------------<br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
#include &lt;windows.h&gt;<br />
//------------------------------------------------------------------------------<br />
VOID out_code( WORD code ,PBUFFER_DATA buffer,PLZW_DATA lzw,PSTACK_DATA stack)<br />
{<br />
 WORD tmp;<br />
 if( code &lt; 0x100 )<br />
 {<br />
  stack-&gt;lp_stack[ stack-&gt;index++ ] = code;<br />
 }<br />
 else<br />
 {<br />
   stack-&gt;lp_stack[ stack-&gt;index++ ] = lzw-&gt;lp_suffix[ code ];<br />
   tmp = lzw-&gt;lp_prefix[ code ];<br />
   while( tmp &gt; 0x100 )<br />
   {<br />
    stack-&gt;lp_stack[ stack-&gt;index++ ] = lzw-&gt;lp_suffix[ tmp ];<br />
    tmp = lzw-&gt;lp_prefix[ tmp ];<br />
   }<br />
   stack-&gt;lp_stack[ stack-&gt;index++ ] = (BYTE)tmp;<br /><br />
 }<br /><br /><br />
 while( stack-&gt;index )<br />
 {<br />
  if( buffer-&gt;index == BUFFERSIZE )<br />
  {<br />
   empty_buffer(lzw,buffer);<br />
  }<br />
  buffer-&gt;lp_buffer[ buffer-&gt;index++ ] = stack-&gt;lp_stack[ --stack-&gt;index ] ;<br />
 }<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID insert_2_table(PLZW_DATA lzw )<br />
{<br /><br />
 lzw-&gt;lp_code[ lzw-&gt;code ]   = lzw-&gt;code;<br />
 lzw-&gt;lp_prefix[ lzw-&gt;code ] = lzw-&gt;prefix;<br />
 lzw-&gt;lp_suffix[ lzw-&gt;code ] = lzw-&gt;suffix;<br />
 lzw-&gt;code++;<br /><br />
 if( lzw-&gt;code == ((WORD)1&lt;&lt;lzw-&gt;cur_code_len)-1 )<br />
 {<br />
  lzw-&gt;cur_code_len++;<br />
  if( lzw-&gt;cur_code_len == CODE_LEN+1 )<br />
      lzw-&gt;cur_code_len = 9;<br />
 }<br />
 if(lzw-&gt;code &gt;= 1&lt;&lt;CODE_LEN )<br />
 {<br />
  re_init_lzw(lzw);<br />
 }<br /><br />
}<br />
//------------------------------------------------------------------------------<br />
WORD get_next_code( PBUFFER_DATA buffer , PLZW_DATA lzw )<br />
{<br /><br />
 BYTE next;<br />
 WORD code;<br />
 while( buffer-&gt;by_left &lt; lzw-&gt;cur_code_len )<br />
 {<br />
  if( buffer-&gt;index == BUFFERSIZE )<br />
  {<br />
   load_buffer( lzw-&gt;h_sour, buffer );<br />
  }<br />
  next = buffer-&gt;lp_buffer[ buffer-&gt;index++ ];<br />
  buffer-&gt;dw_buffer |= (DWORD)next &lt;&lt; (24-buffer-&gt;by_left);<br />
  buffer-&gt;by_left   += 8;<br />
 }<br />
 code = buffer-&gt;dw_buffer &gt;&gt; ( 32 - lzw-&gt;cur_code_len );<br />
 buffer-&gt;dw_buffer &lt;&lt;= lzw-&gt;cur_code_len;<br />
 buffer-&gt;by_left    -= lzw-&gt;cur_code_len;<br /><br />
 return code;<br />
}<br />
//------------------------------------------------------------------------------<br />
VOID do_decode( PBUFFER_DATA in, PBUFFER_DATA out, PLZW_DATA lzw, PSTACK_DATA stack)<br />
{<br />
 WORD code;<br />
 WORD tmp;<br />
 while( in-&gt;index != in-&gt;top  )<br />
 {<br />
  code = get_next_code( in ,lzw );<br /><br />
  if( code &lt; 0x100 )<br />
  {<br />
   // code already in table <br />
   // then simply output the code<br />
   lzw-&gt;suffix = (BYTE)code;<br />
  }<br />
  else <br />
  {<br />
   if( code &lt; lzw-&gt;code  )<br />
   {<br />
    // code also in table <br />
    // then output code chain<br />
    <br />
    tmp = lzw-&gt;lp_prefix[ code ];<br />
    while( tmp &gt; 0x100 )<br />
    {<br />
     tmp = lzw-&gt;lp_prefix[ tmp ];<br />
    }<br />
    lzw-&gt;suffix = (BYTE)tmp;<br />
   }<br />
   else<br />
   {<br />
    // code == lzw-&gt;code<br />
    // code not in table<br />
    // add code into table<br />
    // and out put code <br />
    tmp = lzw-&gt;prefix;<br />
    while( tmp &gt; 0x100 )<br />
    {<br />
     tmp = lzw-&gt;lp_prefix[ tmp ];<br />
    }<br />
    lzw-&gt;suffix = (BYTE)tmp;<br />
   }<br />
  }<br />
  insert_2_table( lzw );<br />
  out_code(code,out,lzw,stack);<br /><br />
  lzw-&gt;prefix = code;<br /><br />
 }<br /><br />
}<br />
//------------------------------------------------------------------------------<br />
VOID decode( HANDLE h_sour, HANDLE h_dest )<br />
{<br />
    LZW_DATA        lzw;<br />
    BUFFER_DATA     in ;<br />
    BUFFER_DATA     out;<br />
 STACK_DATA      stack;<br />
 BOOL   first_run;<br /><br />
 first_run = TRUE;<br /><br /><br />
    lzw_create( &amp;lzw ,h_sour,h_dest );<br />
    buffer_create( &amp;in );<br />
    buffer_create( &amp;out );<br />
 stack_create(&amp;stack );<br /><br />
    while( load_buffer( h_sour, &amp;in ) )<br />
    {<br />
  if( first_run )<br />
  {<br />
   lzw.prefix = get_next_code( &amp;in, &amp;lzw );<br />
   lzw.suffix = lzw.prefix;<br />
   out_code(lzw.prefix, &amp;out, &amp;lzw , &amp;stack);<br />
   first_run = FALSE;<br />
  }<br />
        do_decode(&amp;in , &amp;out, &amp;lzw, &amp;stack);<br />
    }<br /><br />
    empty_buffer( &amp;lzw,&amp;out);<br /><br />
    lzw_destory( &amp;lzw );<br />
    buffer_destory( &amp;in );<br />
    buffer_destory( &amp;out );<br />
 stack_destory( &amp;stack);<br />
}<br /><br />
#endif<br /><br />
2  下面给出一个应用上面模块的简单例子<br /><br />
#include &lt;stdio.h&gt;<br />
#include &lt;stdlib.h&gt;<br />
//------------------------------------------------------------------------------<br /><br />
#include "lzw.h"<br />
#include "hash.h"<br />
#include "fileio.h"<br />
#include "encode.h"<br />
#include "decode.h"<br /><br />
//------------------------------------------------------------------------------<br />
HANDLE h_file_sour;  <br />
HANDLE h_file_dest;<br />
HANDLE h_file;<br />
CHAR*  file_name_in = "d:\\code.c";<br />
CHAR*  file_name_out= "d:\\encode.e";<br />
CHAR*  file_name    = "d:\\decode.d";<br /><br /><br />
//------------------------------------------------------------------------------<br />
int main(int argc, char *argv[]) <br />
{<br />
    h_file_sour = file_handle(file_name_in);<br />
    h_file_dest = file_handle(file_name_out);<br />
    h_file     = file_handle(file_name);<br /><br /><br />
  encode(h_file_sour, h_file_dest);  <br />
// decode(h_file_dest,h_file); <br /><br /><br />
    CloseHandle(h_file_sour); <br />
    CloseHandle(h_file_dest);  <br />
    CloseHandle(h_file);<br /><br />
  return 0;      <br />
}    <br /><br />
3  后语<br /><br />
  之前研究gif文件格式时偶然接触了lzw压缩算法，于是就想自己动手实现。从一开始看人家的原码，然后跟着模仿，到现在用自己的语言表达出来，从理解原理到代码的实现花费了不少时间与精力，但是真正的快乐也就在这里，现在把她拿出来跟大家分享也就是分享快乐。<img src ="http://www.cppblog.com/swo2006/aggbug/11431.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-19 09:45 <a href="http://www.cppblog.com/swo2006/articles/11431.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> Gzip Zlib PNG 压缩算法,源码详解</title><link>http://www.cppblog.com/swo2006/articles/11430.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Sat, 19 Aug 2006 00:51:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11430.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11430.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11430.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11430.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11430.html</trackback:ping><description><![CDATA[
		<table background="/images/point.gif" border="0" cellpadding="6" cellspacing="0" width="99%">
				<tbody>
						<tr>
						</tr>
				</tbody>
		</table>
		我们对算法做三种程度的说明。第一种程度，对gzip所使用压缩算法基本原理的说明。第二种程度，对gzip压缩算法实现方法的说
明。第三种程度，对gzip实现源码级的说明。<br />　<br />
　　1 gzip所使用压缩算法的基本原理<br />
　　<br />
　　gzip 对于要压缩的文件，首先使用lz77算法进行压缩，对得到的结果再使用huffman编码的方法进行压缩。所以我们分别对lz77和huffman编码的原理进行说明。<br />
　　<br /><br />
　　<br />
　　2 gzip压缩算法实现方法<br />
　　<br />
　　2.1 LZ77算法的gzip实现<br />
　　<br />
　　首先，gzip
从要压缩的文件中读入64KB的内容到一个叫window的缓冲区中。为了简单起见，我们以32KB以下文件的压缩为例做说明。对于我们这里使用32KB
以下文件，gzip将整个文件读入到window缓冲区中。然后使用一个叫strstart的变量在window数组中，从0开始一直向后移动。
strstart在每一个位置上，都在它之前的区域中，寻找和当前strstart开始的串的头3个字节匹配的串，并试图从这些匹配串中找到最长的匹配
串。<br />
　　<br />
　　如果当前的strstart开始的串，可以找到最少为3个字节的匹配串的话，当前的strstart开始的匹配长度那么长的串，将会被一个&lt;匹配长度,到匹配串开头的距离&gt;对替换。<br />
　　<br />
　　如果当前的strstart开始的串，找不到任何的最少为3个字节的匹配串的话，那么当前strstart的所在字节将不作改动。<br />
　　<br />
　　为了区分是一个&lt;匹配长度,到匹配串开头的距离&gt;对，还是一个没有被改动的字节，还需要为每一个没有被改动的字节或者&lt;匹配长度,到匹配串开头的距离&gt;对，另外再占用一 <br />
　　位，来进行区分。这位如果为1，表示是一个&lt;匹配长度,到匹配串开头的距离&gt;对，这位如果为0，表示是一个没有被改动的字节。<br />
　　<br />
　　现在来说明一下，为什么最小匹配为3个字节。这是由于，gzip
中，&lt;匹配长度,到匹配串开头的距离&gt;对中，"匹配长度"的范围为3-258，也就是256种可能值，需要8bit来保存。"到匹配串开头的
距离"的范围为0-32K，需要15bit来保存。所以一个&lt;匹配长度,到匹配串开头的距离&gt;对需要23位，差一位3个字节。如果匹配串小于
3个字节的话，使用&lt;匹配长度,到匹配串开头的距离&gt;对进行替换，不但没有压缩，反而还会增大。所以保存&lt;匹配长度,到匹配串开头的距
离&gt;对所需要的位数，决定了最小匹配长度至少要为3个字节。<br />
　　<br />
　　下面我们就来介绍gzip如何实现寻找当前strstart开始的串的最长匹配串。<br />
　　<br />
　　如果每次为当前串寻找匹配串时，都要和之前的每个串的至少3个字节进行比较的话，那么比较量将是非常非常大的。为了提高比较速度，gzip使用了哈希
表。这是gzip实现LZ77的关键。这个哈希表是一个叫head的数组（后面我们将看到为什么这个缓冲区叫head）。gzip对windows中的每
个串，使用串的头三个字节，也就是strstart,strstart+1,strstart+2，用一个设计好的哈希函数来进行计算，得到一个插入位置
ins_h。也就是用串的头三个字节来确定一个插入位置。然后把串的位置，也就是
strstart的值，保存在head数组的第ins_h项中。我们马上就可以看到为什么要这样做。head数组在没有插入任何值时，全部为0。<br /><br />当某处的当前串的三个字节确定了一个ins_h，并把当时当前串的位置也就是当时的strstart保存在了head[ins_h]中。之后另一处，当另
一处的当前串的头三个字节，再为那三个字节时，再使用那个哈希函数来计算，由于是同样的三个字节，同样的哈希函数，得到的ins_h必然和前面得到的
ins_h是相同的。于是就会发现head[ins_h]不为0。这就说明了，有一个头三个字节和自己相同的串把自己的位置保存在了这里，现在head
[ins_h]中保存的值，也就是那个串的开始位置，我们就可以找到那个串，那个串至少前3个字节和当前串的前3个字节相同（稍后我们就可以看到这种说法
不准确，这里是为了说明方便），我们可以找到那个串，做进一步比较，看到底能有多长的匹配。<br />
　　<br />
　　我们现在来说明一下，相同的三个字节，通过哈希函数得到的ins_h必然是相同的。而不同的三个字节，通过哈希函数有没有可能得到同一个ins_h，
我没有对这个哈希函数做研究，并不清楚，不过一般的哈希函数都是这样的，所以极大可能这里的也会是这种情况，即不同的三个字节，通过哈希函数有可能得到同
一个ins_h，不过这并不要紧，我们发现有可能是匹配串之后，还会进行串的比较。<br />
　　<br />
　　一个文件中，可能有很多个串的头三个字节都是相同的，也就是说他们计算得到的ins_h都是相同的，如何能保证找到他们中的每一个串呢？gzip使用
一个链把他们链在一起。gzip每次把当前串的位置插入head的当前串头三个字节算出的ins_h处时，都会首先把原来的head[ins_h]的值，
保存到一个叫prev的数组中，保存的位置就在现在的strstart处。这样当以后某处的当前串计算出ins_h，发现head[ins_h]不空时，
就可以到prev[ head[ins_h] ]中找到更前一个的头三个字节相同的串的位置。对此我们举例说明。<br />
　　<br />
　　例，串<br />
　　0abcdabceabcfabcg<br />
　　^^^^^^^^^^^^^^^^^<br />
　　01234567890123456<br />
　　<br />
　　整个串被压缩程序处理之后。<br />
　　<br />
　　由abc算出ins_h。<br />
　　这时的head[ins_h]中为 13,即"abcg"的开始位置。<br />
　　这时prev[13]中为 9，即"abcfabcg"的开始位置。<br />
　　这时prev[9]中为 5，即"abceabcfabcg"的开始位置。<br />
　　这时prev[5]中为 1，即"abcdabceabcfabcg"的开始位置。<br />
　　这时prev[1]中为 0。<br />
　　<br />
　　我们看到所有头三个字母为abc的串，被链在了一起，从head可以一直找下去，直到找到0。<br />
　　<br />
　　现在我们也就知道了，三个字节通过哈希函数计算得到同一ins_h的所有的串被链在了一起，head[ins_h]为链头，prev数组中放着的更早的串。这也就是head和prev名称的由 <br />
　　来。<br />
　　<br />
　　gzip寻找匹配串的另外一个值得注意的实现是，延迟匹配。会进行两次尝试。比如当前串为str,那么str发生匹配以后，并不发生压缩，还会对str+1串进行匹配，然后看哪种 <br />
　　匹配效果好。<br />
　　<br />
　　例子 ...<br />　从这个例子中我们就看到了做另外一次尝试的原因。如果碰到的一个匹配就使用了的话，可能错过更长匹配的机会。现在做两次会有所改善。<br />
　　<br />
　　...<br />
　　<br />
　　2.2 问题讨论<br />
　　<br />
　　我在这里对gzip压缩算法做出了一些说明，是希望可以和对gzip或者压缩解压缩感兴趣的朋友进行交流。<br />
　　我对gzip的了解要比这里说的更多一些，也有更多的例子。如果哪位朋友愿意对下面的问题进行研究，以及其他压缩解压缩的问题进行研究，来这里
http://jiurl.cosoft.org.cn/forum/ 和我交流的话，我也愿意就我知道的内容进行更多的说明。<br />
　　<br />
　　下面是几个问题<br />
　　<br />
　　这种匹配算法，即用3个字节(最小匹配)来计算一个整数，是否比用串比较来得高效，高效到什么程度。<br />
　　<br />
　　哈希函数的讨论。不同的三个字节，是否可能得到同一个ins_h。ins_h和计算它的三个字节的关系。<br />
　　<br />
　　几次延迟尝试比较好？<br />
　　<br />
　　用延迟，两次尝试是否对压缩率的改善是非常有限的？<br />
　　<br />
　　影响lz77压缩率的因素。<br />
　　<br />
　　压缩的极限。<br />
　　　　<br />
　　2.3 ...<br />
　　<br />
　　3 gzip源码分析<br />
　　<br />
　　main() 中调用函数 treat_file() 。<br />
　　treat_file() 中打开文件，调用函数 zip()。注意这里的 work 的用法，这是一个函数指针。<br />
　　zip() 中输出gzip文件格式的头，调用 bi_init，ct_init，lm_init，<br />
　　其中在lm_init中将 head 初始化清0。初始化strstart为0。从文件中读入64KB的内容到window缓冲区中。<br />
　　由于计算strstart=0时的ins_h，需要0,1,2这三个字节和哈希函数发生关系，所以在lm_init中，预读0,1两个字节，并和哈希函数发生关系。<br />
　　<br />
　　然后lm_init调用 deflate()。<br />
　　deflate() gzip的LZ77的实现主要deflate()中。<br />
　　...<br /><img src ="http://www.cppblog.com/swo2006/aggbug/11430.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-19 08:51 <a href="http://www.cppblog.com/swo2006/articles/11430.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>A Brief History of zlib</title><link>http://www.cppblog.com/swo2006/articles/11410.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Fri, 18 Aug 2006 08:23:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11410.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11410.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11410.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11410.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11410.html</trackback:ping><description><![CDATA[
		<p>
				<b>A Brief History of zlib</b>
		</p>
		<p>The origins of zlib can be found in the history of Info-ZIP. Info-ZIP
is loosely organized group of programmers who give the following reason
for their existence: </p>
		<p>
				<i>Info-ZIP's purpose is to provide free, portable, high-quality versions
of the Zip and UnZip compressor-archiver utilities that are compatible
with the DOS-based PKZIP by PKWARE, Inc. </i>
		</p>
		<p>These free versions of Zip and UnZip are world class programs, and are
in wide use on platforms ranging from the orphaned Amiga through MS-DOS
PCs up to high powered RISC workstations. But these programs are designed
to be used as command line utilities, not as library routines. People have
found that porting the Info-ZIP source into an application could be a grueling
exercise. </p>
		<p>Fortunately for all of us, two of the Info-ZIP gurus took it upon themselves
to solve this problem. Mark Adler and Jean-loup Gailly single-handedly
created z<i>lib</i>, a set of library routines that provide a safe, free,
and unpatented implementation of the deflate compression algorithm. </p>
		<p>One of the driving reasons behind zlib's creation was for use as the
compressor for PNG format graphics. After Unisys belatedly began asserting
their patent rights to LZW compression, programmers all over the world
were thrown into a panic over the prospect of paying royalties on their
GIF decoding programs. The PNG standard was created to provide an unencumbered
format for graphics interchange. The zlib version of the deflate algorithm
was embraced by PNG developers, not only because it was free, but it also
compressed better than the original LZW compressor used in GIF files. </p>
		<p>zlib turns out to be good for more than graphics developers, however.
The deflate algorithm makes an excellent general purpose compressor, and
as such can be incorporated into all sorts of different software. For example,
I use zlib as the compression engine in Greenleaf's ArchiveLib, a data
compression library that work with ZIP archives. It's performance and compatibility
mean I didn't have to reinvent the wheel, saving precious months of development
time. </p>
		<p>
				<b>zlib's interface</b>
		</p>
		<p>As a library developer, I know that interfaces make or break a library.
Performance issues are important, but if an awkward API makes it impossible
to integrate a library into your program, you've got a problem. </p>
		<p>zlib's interface is confined to just a few simple function calls. The
entire state of a given compression or decompression session is encapsulated
in a C structure of type <i>z_stream</i>, whose definition is shown in
Figure 1. </p>
		<pre>typedef struct z_stream_s {<br />  Bytef *next_in;    /* next input byte */<br />  uInt avail_in;     /* number of bytes available at next_in */<br />  uLong total_in;    /* count of input bytes read so far */<br />  Bytef *next_out;   /* next output byte should be put there */<br />  uInt avail_out;    /* remaining free space at next_out */<br />  uLong total_out;   /* count of bytes output so far */<br />  char *msg;         /* last error message, NULL if no error */<br />  struct internal_state *state; /* not visible by applications*/<br />  alloc_func zalloc; /* used to allocate the internal state*/<br />  free_func zfree;   /* used to free the internal state */<br />  voidpf opaque;     /* private data passed to zalloc and zfree*/<br />  int data_type;     /* best guess about the data: ascii or binary*/<br />  uLong adler;       /* adler32 value of the uncompressed data */<br />  uLong reserved;    /* reserved for future use */<br />} z_stream;<br /></pre>
		<center>
				<p>Figure 1
<br />The z_stream object definition </p>
		</center>
		<p>Using the library to compress or decompress a file or other data object
consists of three main steps: </p>
		<ul>
				<li>Creating a <i>z_stream</i> object. </li>
				<li>Processing input and output, using the <i>z_stream</i> object to communicate
with zlib. </li>
				<li>Destroying the <i>z_stream</i> object. </li>
		</ul>
		<p>An overview of the process is shown in Figure 2. </p>
		<center>
				<p>
						<img src="http://www.dogma.net/markn/articles/zlibtool/Figure2.GIF" border="0" height="538" width="210" />
				</p>
		</center>
		<center>
				<p>Figure 2
<br />The compression or decompression process </p>
		</center>
		<p>Steps 1 and 3 of the compression process are done using conventional
function calls. The zlib API, documented in header file <i>zlib.h</i>,
prototypes the following functions for initialization and termination of
the compression or decompression process: 
<br /></p>
		<ul>
				<li>
						<i>deflateInit()</i>
				</li>
				<li>
						<i>inflateInit()</i>
				</li>
				<li>
						<i>deflateEnd()</i>
				</li>
				<li>
						<i>inflateEnd()</i>
				</li>
		</ul>
		<p>Step 2 is done via repeated calls to either <i>inflate()</i> or <i>deflate()</i>,
passing the <i>z_stream</i> object as a parameter. The entire state of
the process is contained in that object, so there are no global flags or
variables, which allows the library to be completely reentrant. Storing
the state of the process in a single object also cuts down on the number
of parameters that must be passed to the API functions. </p>
		<p>When performing compression or decompression, zlib doesn't perform any
I/O on its own. Instead, it reads data from an input buffer pointer that
you supply in the z_stream object. You simply set up a pointer to the next
block of input data in member next_in, and place the number of available
bytes in the avail_in member. Likewise, zlib writes its output data to
a memory buffer you set up in the next_out member. As it writes output
bytes, zlib decrements the avail_out member until it drops to 0. </p>
		<p>Given this interface, Step 2 of the compression process for an input
file and an output file might look something like this: </p>
		<pre>z_stream z;<br />char input_buffer[ 1024 ];<br />char output_buffer[ 1024 ];<br />FILE *fin;<br />FILE *fout;<br />int status;<br />...<br />    z.avail_in = 0;<br />    z.next_out = output_buffer;<br />    z.avail_out = 1024;<br />    for ( ; ; ) {<br />        if ( z.avail_in == 0 ) {<br />            z.next_in = input_buffer;<br />            z.avail_in = fread( input_buffer, 1, 1024, fin );<br />        }<br />        if ( z.avail_in == 0 )<br />             break;<br />        status = deflate( &amp;z, Z_NO_FLUSH );<br />        int count = 1024 - z.avail_out;<br />        if ( count )<br />            fwrite( output_buffer, 1, count, fout );<br />        z.next_out = output_buffer;<br />        z.avail_out = 1024;<br />    }<br /></pre>
		<center>
				<p>Figure 3 
<br />The code to implement file compression </p>
		</center>
		<p>This method of handling I/O frees zlib from having to implement system
dependent read and write code, and it insures that you can use the library
to compress any sort of input stream, not just files. It's simply a matter
of replacing the wrapper code shown above with a version customized for
your data stream. </p>
		<p>
				<b>Wrapping it up</b>
		</p>
		<p>zlib's versatility is one of its strengths, but I don't always need
all that flexibility. For example, to perform the simple file compression
task Scott asked about at the start of this article, it would be nice to
just be able to call a single function to compress a file, and another
function to decompress. To make this possible, I created a wrapper class
called zlibEngine. </p>
		<p>zlibEngine provides a simple API that automates the compression and
decompression of files and uses virtual functions to let you customize
your user interface to zlib. The class definition is shown in its entirety
in Figure 4. There are two different groups of members that are important
to you in ZlibEngine. The first is the set of functions providing the calling
interface to the engine. The second is the set of functions and data members
used to create a user interface that is active during the compression process.
</p>
		<pre>class ZlibEngine : public z_stream {<br />    public :<br />        ZlibEngine();<br />            int compress( const char *input,<br />                          const char *output,<br />                          int level = 6 );<br />        int decompress( const char *input,<br />                        const char *output );<br />        void set_abort_flag( int i ){ m_AbortFlag = i; }<br />    protected :<br />        int percent();<br />        int load_input();<br />        int flush_output();<br />    protected :<br />        virtual void progress( int percent ){};<br />        virtual void status( char *message ){};<br />    protected :<br />        int m_AbortFlag;<br />        FILE *fin;<br />        FILE *fout;<br />        long length;<br />        int err;<br />        enum { input_length = 4096 };<br />        unsigned char input_buffer[ input_length ];<br />        enum { output_length = 4096 };<br />        unsigned char output_buffer[ output_length ];<br />};<br /></pre>
		<center>
				<p>Figure 4
<br />The ZlibEngine wrapper class </p>
		</center>
		<p>
				<b>The Calling API</b>
		</p>
		<p>There are three C++ functions that implement the API needed to perform
simple compression and decompression. Before using the engine, you must
call the constructor, the first function. Since <i>ZlibEngine</i> is derived
from the <i>z_stream</i> object used as the interface to zlib, the constructor
is in effect also creating a <i>z_stream </i>object that will be used to
communicate with zlib. In addition, the constructor initializes some of
the <i>z_stream </i>member variables that will be used in either compression
or decompression. </p>
		<p>The two remaining functions are nice and simple: <i>compress()</i> compresses
a file using the deflate algorithm. An optional level parameter sets a
compression factor between 9 (maximum compression) and 0 (no compression.)
<i>decompress()</i> decompresses a file, as you would expect. The compression
level parameter isn't necessary when decompressing, due to the nature of
the deflate algorithm. Both of these functions return an integer status
code, defined in the zlib header file <i>zlib.h</i>. <i>Z_OK</i> is returned
when everything works as expected. Note that I added an additional code,
<i>Z_USER_ABORT</i>, used for an end user abort of the compression or decompression
process. </p>
		<p>The wrapper class makes it much easier to compress or decompress files
using zlib. You only need to remember three things: </p>
		<ul>
				<li>Include the header file for the wrapper class, <i>zlibengn.h</i>. </li>
				<li>Construct a <i>ZlibEngine</i> object. </li>
				<li>Call the member functions <i>compress()</i> or <i>decompress()</i>
to do the actual work. </li>
		</ul>
		<p>This means you can now perform compression with code this simple: </p>
		<pre>#include &lt;zlibengn.h&gt;<br /><br />int foo()<br />{<br />    ZlibEngine engine;<br />    return engine.compress( "INPUT.DAT", "INPUT.DA_");<br />}<br /></pre>
		<p>That's about as simple as you could ask for, isn't it? </p>
		<p>
				<b>The User Interface API</b>
		</p>
		<p>The calling API doesn't really make much of a case for creating the
<i>ZlibEngine</i> class. Based on what you've seen so far, the <i>compress()</i>
and <i>decompress()</i> functions don't really need to be members of a
class. In theory, a global <i>compress()</i> function could just instantiate
a <i>z_stream</i> object when called, without the caller even being aware
of it. </p>
		<p>The reason for creating this engine class is found in a completely different
area: the user interface. It's really nice to be able to track the progress
of your compression job while it's running. Conventional C libraries have
to make do with callback functions or inflexible standardized routines
in order to provide feedback, but C++ offers a better alternative through
the use of virtual functions. </p>
		<p>The <i>ZlibEngine</i> class has two virtual functions that are used
to create a useful user interface: <i>progress()</i> is called periodically
during the compression or decompression process, with a single integer
argument that tells what percentage of the input file has been processed.
<i>status()</i> is called with status messages during processing. </p>
		<p>Both of these virtual functions have access to the <i>ZlibEngine</i>
protected data element, <i>m_AbortFlag</i>. Setting this flag to a non-zero
value will cause the compression or decompression routine to abort immediately.
This easily takes care of another sticky user interface problem found when
using library code. </p>
		<p>Writing your own user interface then becomes a simple exercise. You
simply derive a new class from <i>ZlibEngine</i>, and define your own versions
of one or both of these virtual functions. Instantiate an object of your
class instead of <i>ZlibEngine</i>, and your user interface can be as spiffy
and responsive as you like! </p>
		<p>
				<b>Command line compression</b>
		</p>
		<p>I wrote a simple command line test program to demonstrate the use of
class <i>ZlibEngine</i>. <i>zlibtest.cpp</i> does a simple compress/decompress
cycle of the input file specified on the command line. I implement a progress
function that simply prints out the current percent towards completion
as the file is processed: </p>
		<pre>class MyZlibEngine : public ZlibEngine {<br />    public :<br />        void progress( int percent )<br />        {<br />            printf( "%3d%%\b\b\b\b", percent );<br />            if ( kbhit() ) {<br />                getch();<br />                m_AbortFlag = 1;<br />            }<br />        } <br />};<br /></pre>
		<p>Since class <i>ZlibEngine</i> is so simple, the derived class doesn't
even have to implement a constructor or destructor. The derived version
of <i>progress()</i> is able to provide user feedback as well as an abort
function with just a few lines of code. <i>zlibtest.cpp</i> is shown in
its entirety in Listing 1. </p>
		<p>
				<b>The OCX</b>
		</p>
		<p>To provide a slightly more complicated test of class <i>ZlibEngine</i>,
I created a 32 bit OCX using Visual C++ 4.1. The interface to an OCX is
defined in terms of methods, events, and properties. <i>ZlibTool.ocx</i>
has the following interface: </p>
		<table align="center">
				<tbody>
						<tr>
								<td align="left" width="127">
										<center>
												<p>
														<b>Properties:</b>
												</p>
										</center>
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p> InputFile </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p>OutputFile </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p>Level </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p>Status </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<br />
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<center>
												<p>
														<b>Methods:</b>
												</p>
										</center>
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p> Compress() </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p>Decompress() </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p>Abort() </p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<br />
								</td>
								<td align="left" valign="bottom" width="156">
										<br />
								</td>
						</tr>
						<tr>
								<td align="left" width="127">
										<center>
												<p>
														<b>Events:</b>
												</p>
										</center>
								</td>
								<td align="left" valign="bottom" width="156">
										<center>
												<p> Progress() </p>
										</center>
								</td>
						</tr>
				</tbody>
		</table>
		<p>(Note that I chose to pass status information from the OCX using a property,
not an event.) </p>
		<p>
				<i>ZlibTool.ocx</i> is a control derived from a standard Win32 progress
bar. The progress care gets updated automatically while compressing or
decompressing, so you get some user interface functionality for free. Using
it with Visual Basic 4.0 or Delphi 2.0 becomes a real breeze. After registering
the OCX, you can drop a copy of it onto your form and use it with a minimal
amount of coding. </p>
		<p>Both the source code for the OCX and a sample Delphi 2.0 program are
available on the DDJ listing service. A screen shot of the Delphi program
in action is shown in Figure 5. </p>
		<center>
				<p>
						<img src="http://www.dogma.net/markn/articles/zlibtool/IMG00002.GIF" border="0" height="353" width="415" />
				</p>
		</center>
		<center>
				<p>Figure 5 </p>
		</center>
		<center>
				<p>The Delphi 2.0 OCX test program </p>
		</center>
		<p>
				<b>Reference material</b>
		</p>
		<p>The source code that accompanies this article can be downloaded from
this Web page. It contains the following source code collections: </p>
		<ul>
				<li>The complete source for zlib </li>
				<li>The Visual C++ 4.1 project for the ZlibTool OCX </li>
				<li>The Delphi 2.0 project that exercises the OCX </li>
				<li>The Console test program that exercises the ZlibEngine class </li>
		</ul>
		<p>Each of the subdirectories contains a README.TXT file with documentation
describing how to build and use the programs. </p>
		<p>The source is split into two archives: </p>
		<table>
				<tbody>
						<tr>
								<td valign="top" width="157">
										<center>
												<p>
														<a href="http://www.dogma.net/markn/articles/zlibtool/zlibtool.zip">zlibtool.zip</a>
												</p>
										</center>
								</td>
								<td width="258">
										<center>
												<p>All source code and the OCX file.</p>
										</center>
								</td>
						</tr>
						<tr>
								<td valign="top" width="157">
										<center>
												<p>
														<a href="http://www.dogma.net/markn/articles/zlibtool/zlibdll.zip">zlibdll.zip</a>
												</p>
										</center>
								</td>
								<td width="258">
										<center>
												<p>The supporting MFC and VC++ DLLs. Many people will already have
these files on their systems: MFC40.DLL, MSVCRT40.DLL, and OLEPRO32.DLL.
</p>
										</center>
								</td>
						</tr>
				</tbody>
		</table>
		<p>I haven't discussed the zlib code itself in this article. The best place
to start gathering information about how to use zlib and the Info-ZIP products
can be found on their home pages. Both pages have links to the most current
versions of their source code as well: </p>
		<table align="center">
				<tbody>
						<tr>
								<td align="left" valign="middle" width="157">
										<center>
												<p>Info-ZIP</p>
										</center>
								</td>
								<td align="left" valign="middle" width="258">
										<center>
												<p>
														<a href="http://www.info-zip.org/">http://www.info-zip.org
</a>
												</p>
										</center>
								</td>
						</tr>
						<tr>
								<td align="left" valign="middle" width="157">
										<center>
												<p>zlib</p>
										</center>
								</td>
								<td align="left" valign="middle" width="258">
										<center>
												<p>
														<a href="http://www.gzip.org/zlib">http://www.gzip.org/zlib</a>
												</p>
										</center>
								</td>
						</tr>
				</tbody>
		</table>
		<p>Once you download the Info-ZIP code, the quick start documentation is
found in source file <i>zlib.h</i>. If you cook up any useful code that
uses zlib, you might want to forward copies to <a href="mailto:newt@uchicago.edu">Greg
Roelofs</a> for inclusion on the zlib home page. Greg maintains the zlib
pages, and you can reach him via links found there. </p>
		<p>
				<b>Feel-good plug</b>
		</p>
		<p>zlib can do a lot more than just compress files. Its versatile interface
can be used for streaming I/O, in-memory compression, and more. Since Jean-loup
Gailly and Mark Adler were good enough to make this capable tool available
to the public, it only makes sense that we take advantage of it. I know
I have, and I encourage you to do the same. </p>
<img src ="http://www.cppblog.com/swo2006/aggbug/11410.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-18 16:23 <a href="http://www.cppblog.com/swo2006/articles/11410.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ZLIB Compressed Data Format Specification version 3.3</title><link>http://www.cppblog.com/swo2006/articles/11407.html</link><dc:creator>swo</dc:creator><author>swo</author><pubDate>Fri, 18 Aug 2006 06:40:00 GMT</pubDate><guid>http://www.cppblog.com/swo2006/articles/11407.html</guid><wfw:comment>http://www.cppblog.com/swo2006/comments/11407.html</wfw:comment><comments>http://www.cppblog.com/swo2006/articles/11407.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/swo2006/comments/commentRss/11407.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/swo2006/services/trackbacks/11407.html</trackback:ping><description><![CDATA[
		<pre>ZLIB Compressed Data Format Specification version 3.3<br /><br />Status of This Memo<br /><br />   This memo provides information for the Internet community.  This memo<br />   does not specify an Internet standard of any kind.  Distribution of<br />   this memo is unlimited.<br /><br />IESG Note:<br /><br />   The IESG takes no position on the validity of any Intellectual<br />   Property Rights statements contained in this document.<br /><br />Notices<br /><br />   Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly<br /><br />   Permission is granted to copy and distribute this document for any<br />   purpose and without charge, including translations into other<br />   languages and incorporation into compilations, provided that the<br />   copyright notice and this notice are preserved, and that any<br />   substantive changes or deletions from the original are clearly<br />   marked.<br /><br />   A pointer to the latest version of this and related documentation in<br />   HTML format can be found at the URL<br />   &lt;<a href="ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html">ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html</a>&gt;.<br /><br />Abstract<br /><br />   This specification defines a lossless compressed data format.  The<br />   data can be produced or consumed, even for an arbitrarily long<br />   sequentially presented input data stream, using only an a priori<br />   bounded amount of intermediate storage.  The format presently uses<br />   the DEFLATE compression method but can be easily extended to use<br />   other compression methods.  It can be implemented readily in a manner<br />   not covered by patents.  This specification also defines the ADLER-32<br />   checksum (an extension and improvement of the Fletcher checksum),<br />   used for detection of data corruption, and provides an algorithm for<br />   computing it.<br /><br />Table of Contents<br /><br />   1. Introduction ................................................... 2<br />      1.1. Purpose ................................................... 2<br />      1.2. Intended audience ......................................... 3<br />      1.3. Scope ..................................................... 3<br />      1.4. Compliance ................................................ 3<br />      1.5.  Definitions of terms and conventions used ................ 3<br />      1.6. Changes from previous versions ............................ 3<br />   2. Detailed specification ......................................... 3<br />      2.1. Overall conventions ....................................... 3<br />      2.2. Data format ............................................... 4<br />      2.3. Compliance ................................................ 7<br />   3. References ..................................................... 7<br />   4. Source code .................................................... 8<br />   5. Security Considerations ........................................ 8<br />   6. Acknowledgements ............................................... 8<br />   7. Authors' Addresses ............................................. 8<br />   8. Appendix: Rationale ............................................ 9<br />   9. Appendix: Sample code ..........................................10<br /><br />1. Introduction<br /><br />   1.1. Purpose<br /><br />      The purpose of this specification is to define a lossless<br />      compressed data format that:<br /><br />          * Is independent of CPU type, operating system, file system,<br />            and character set, and hence can be used for interchange;<br /><br />          * Can be produced or consumed, even for an arbitrarily long<br />            sequentially presented input data stream, using only an a<br />            priori bounded amount of intermediate storage, and hence can<br />            be used in data communications or similar structures such as<br />            Unix filters;<br /><br />          * Can use a number of different compression methods;<br /><br />          * Can be implemented readily in a manner not covered by<br />            patents, and hence can be practiced freely.<br /><br />      The data format defined by this specification does not attempt to<br />      allow random access to compressed data.<br /><br />   1.2. Intended audience<br /><br />      This specification is intended for use by implementors of software<br />      to compress data into zlib format and/or decompress data from zlib<br />      format.<br /><br />      The text of the specification assumes a basic background in<br />      programming at the level of bits and other primitive data<br />      representations.<br /><br />   1.3. Scope<br /><br />      The specification specifies a compressed data format that can be<br />      used for in-memory compression of a sequence of arbitrary bytes.<br /><br />   1.4. Compliance<br /><br />      Unless otherwise indicated below, a compliant decompressor must be<br />      able to accept and decompress any data set that conforms to all<br />      the specifications presented here; a compliant compressor must<br />      produce data sets that conform to all the specifications presented<br />      here.<br /><br />   1.5.  Definitions of terms and conventions used<br /><br />      byte: 8 bits stored or transmitted as a unit (same as an octet).<br />      (For this specification, a byte is exactly 8 bits, even on<br />      machines which store a character on a number of bits different<br />      from 8.) See below, for the numbering of bits within a byte.<br /><br />   1.6. Changes from previous versions<br /><br />      Version 3.1 was the first public release of this specification.<br />      In version 3.2, some terminology was changed and the Adler-32<br />      sample code was rewritten for clarity.  In version 3.3, the<br />      support for a preset dictionary was introduced, and the<br />      specification was converted to RFC style.<br /><br />2. Detailed specification<br /><br />   2.1. Overall conventions<br /><br />      In the diagrams below, a box like this:<br /><br />         +---+<br />         |   | &lt;-- the vertical bars might be missing<br />         +---+<br /><br />      represents one byte; a box like this:<br /><br />         +==============+<br />         |              |<br />         +==============+<br /><br />      represents a variable number of bytes.<br /><br />      Bytes stored within a computer do not have a "bit order", since<br />      they are always treated as a unit.  However, a byte considered as<br />      an integer between 0 and 255 does have a most- and least-<br />      significant bit, and since we write numbers with the most-<br />      significant digit on the left, we also write bytes with the most-<br />      significant bit on the left.  In the diagrams below, we number the<br />      bits of a byte so that bit 0 is the least-significant bit, i.e.,<br />      the bits are numbered:<br /><br />         +--------+<br />         |76543210|<br />         +--------+<br /><br />      Within a computer, a number may occupy multiple bytes.  All<br />      multi-byte numbers in the format described here are stored with<br />      the MOST-significant byte first (at the lower memory address).<br />      For example, the decimal number 520 is stored as:<br /><br />             0     1<br />         +--------+--------+<br />         |00000010|00001000|<br />         +--------+--------+<br />          ^        ^<br />          |        |<br />          |        + less significant byte = 8<br />          + more significant byte = 2 x 256<br /><br />   2.2. Data format<br /><br />      A zlib stream has the following structure:<br /><br />           0   1<br />         +---+---+<br />         |CMF|FLG|   (more--&gt;)<br />         +---+---+<br /><br />      (if FLG.FDICT set)<br /><br />           0   1   2   3<br />         +---+---+---+---+<br />         |     DICTID    |   (more--&gt;)<br />         +---+---+---+---+<br /><br />         +=====================+---+---+---+---+<br />         |...compressed data...|    ADLER32    |<br />         +=====================+---+---+---+---+<br /><br />      Any data which may appear after ADLER32 are not part of the zlib<br />      stream.<br /><br />      CMF (Compression Method and flags)<br />         This byte is divided into a 4-bit compression method and a 4-<br />         bit information field depending on the compression method.<br /><br />            bits 0 to 3  CM     Compression method<br />            bits 4 to 7  CINFO  Compression info<br /><br />      CM (Compression method)<br />         This identifies the compression method used in the file. CM = 8<br />         denotes the "deflate" compression method with a window size up<br />         to 32K.  This is the method used by gzip and PNG (see<br />         references [1] and [2] in Chapter 3, below, for the reference<br />         documents).  CM = 15 is reserved.  It might be used in a future<br />         version of this specification to indicate the presence of an<br />         extra field before the compressed data.<br /><br />      CINFO (Compression info)<br />         For CM = 8, CINFO is the base-2 logarithm of the LZ77 window<br />         size, minus eight (CINFO=7 indicates a 32K window size). Values<br />         of CINFO above 7 are not allowed in this version of the<br />         specification.  CINFO is not defined in this specification for<br />         CM not equal to 8.<br /><br />      FLG (FLaGs)<br />         This flag byte is divided as follows:<br /><br />            bits 0 to 4  FCHECK  (check bits for CMF and FLG)<br />            bit  5       FDICT   (preset dictionary)<br />            bits 6 to 7  FLEVEL  (compression level)<br /><br />         The FCHECK value must be such that CMF and FLG, when viewed as<br />         a 16-bit unsigned integer stored in MSB order (CMF*256 + FLG),<br />         is a multiple of 31.<br /><br />      FDICT (Preset dictionary)<br />         If FDICT is set, a DICT dictionary identifier is present<br />         immediately after the FLG byte. The dictionary is a sequence of<br />         bytes which are initially fed to the compressor without<br />         producing any compressed output. DICT is the Adler-32 checksum<br />         of this sequence of bytes (see the definition of ADLER32<br />         below).  The decompressor can use this identifier to determine<br />         which dictionary has been used by the compressor.<br /><br />      FLEVEL (Compression level)<br />         These flags are available for use by specific compression<br />         methods.  The "deflate" method (CM = 8) sets these flags as<br />         follows:<br /><br />            0 - compressor used fastest algorithm<br />            1 - compressor used fast algorithm<br />            2 - compressor used default algorithm<br />            3 - compressor used maximum compression, slowest algorithm<br /><br />         The information in FLEVEL is not needed for decompression; it<br />         is there to indicate if recompression might be worthwhile.<br /><br />      compressed data<br />         For compression method 8, the compressed data is stored in the<br />         deflate compressed data format as described in the document<br />         "DEFLATE Compressed Data Format Specification" by L. Peter<br />         Deutsch. (See reference [3] in Chapter 3, below)<br /><br />         Other compressed data formats are not specified in this version<br />         of the zlib specification.<br /><br />      ADLER32 (Adler-32 checksum)<br />         This contains a checksum value of the uncompressed data<br />         (excluding any dictionary data) computed according to Adler-32<br />         algorithm. This algorithm is a 32-bit extension and improvement<br />         of the Fletcher algorithm, used in the ITU-T X.224 / ISO 8073<br />         standard. See references [4] and [5] in Chapter 3, below)<br /><br />         Adler-32 is composed of two sums accumulated per byte: s1 is<br />         the sum of all bytes, s2 is the sum of all s1 values. Both sums<br />         are done modulo 65521. s1 is initialized to 1, s2 to zero.  The<br />         Adler-32 checksum is stored as s2*65536 + s1 in most-<br />         significant-byte first (network) order.<br /><br />   2.3. Compliance<br /><br />      A compliant compressor must produce streams with correct CMF, FLG<br />      and ADLER32, but need not support preset dictionaries.  When the<br />      zlib data format is used as part of another standard data format,<br />      the compressor may use only preset dictionaries that are specified<br />      by this other data format.  If this other format does not use the<br />      preset dictionary feature, the compressor must not set the FDICT<br />      flag.<br /><br />      A compliant decompressor must check CMF, FLG, and ADLER32, and<br />      provide an error indication if any of these have incorrect values.<br />      A compliant decompressor must give an error indication if CM is<br />      not one of the values defined in this specification (only the<br />      value 8 is permitted in this version), since another value could<br />      indicate the presence of new features that would cause subsequent<br />      data to be interpreted incorrectly.  A compliant decompressor must<br />      give an error indication if FDICT is set and DICTID is not the<br />      identifier of a known preset dictionary.  A decompressor may<br />      ignore FLEVEL and still be compliant.  When the zlib data format<br />      is being used as a part of another standard format, a compliant<br />      decompressor must support all the preset dictionaries specified by<br />      the other format. When the other format does not use the preset<br />      dictionary feature, a compliant decompressor must reject any<br />      stream in which the FDICT flag is set.<br /><br />3. References<br /><br />   [1] Deutsch, L.P.,"GZIP Compressed Data Format Specification",<br />       available in <a href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a><br /><br />   [2] Thomas Boutell, "PNG (Portable Network Graphics) specification",<br />       available in <a href="ftp://ftp.uu.net/graphics/png/documents/">ftp://ftp.uu.net/graphics/png/documents/</a><br /><br />   [3] Deutsch, L.P.,"DEFLATE Compressed Data Format Specification",<br />       available in <a href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a><br /><br />   [4] Fletcher, J. G., "An Arithmetic Checksum for Serial<br />       Transmissions," IEEE Transactions on Communications, Vol. COM-30,<br />       No. 1, January 1982, pp. 247-252.<br /><br />   [5] ITU-T Recommendation X.224, Annex D, "Checksum Algorithms,"<br />       November, 1993, pp. 144, 145. (Available from<br /><a href="gopher://info.itu.ch/">gopher://info.itu.ch</a>). ITU-T X.244 is also the same as ISO 8073.<br /><br />4. Source code<br /><br />   Source code for a C language implementation of a "zlib" compliant<br />   library is available at <a href="ftp://ftp.uu.net/pub/archiving/zip/zlib/">ftp://ftp.uu.net/pub/archiving/zip/zlib/</a>.<br /><br />5. Security Considerations<br /><br />   A decoder that fails to check the ADLER32 checksum value may be<br />   subject to undetected data corruption.<br /><br />6. Acknowledgements<br /><br />   Trademarks cited in this document are the property of their<br />   respective owners.<br /><br />   Jean-Loup Gailly and Mark Adler designed the zlib format and wrote<br />   the related software described in this specification.  Glenn<br />   Randers-Pehrson converted this document to RFC and HTML format.<br /><br />7. Authors' Addresses<br /><br />   L. Peter Deutsch<br />   Aladdin Enterprises<br />   203 Santa Margarita Ave.<br />   Menlo Park, CA 94025<br /><br />   Phone: (415) 322-0103 (AM only)<br />   FAX:   (415) 322-1734<br />   EMail: &lt;<a href="mailto:ghost@aladdin.com">ghost@aladdin.com</a>&gt;<br /><br />   Jean-Loup Gailly<br /><br />   EMail: &lt;<a href="mailto:gzip@prep.ai.mit.edu">gzip@prep.ai.mit.edu</a>&gt;<br /><br />   Questions about the technical content of this specification can be<br />   sent by email to<br /><br />   Jean-Loup Gailly &lt;<a href="mailto:gzip@prep.ai.mit.edu">gzip@prep.ai.mit.edu</a>&gt; and<br />   Mark Adler &lt;<a href="mailto:madler@alumni.caltech.edu">madler@alumni.caltech.edu</a>&gt;<br /><br />   Editorial comments on this specification can be sent by email to<br /><br />   L. Peter Deutsch &lt;<a href="mailto:ghost@aladdin.com">ghost@aladdin.com</a>&gt; and<br />   Glenn Randers-Pehrson &lt;<a href="mailto:randeg@alumni.rpi.edu">randeg@alumni.rpi.edu</a>&gt;<br /><br />8. Appendix: Rationale<br /><br />   8.1. Preset dictionaries<br /><br />      A preset dictionary is specially useful to compress short input<br />      sequences. The compressor can take advantage of the dictionary<br />      context to encode the input in a more compact manner. The<br />      decompressor can be initialized with the appropriate context by<br />      virtually decompressing a compressed version of the dictionary<br />      without producing any output. However for certain compression<br />      algorithms such as the deflate algorithm this operation can be<br />      achieved without actually performing any decompression.<br /><br />      The compressor and the decompressor must use exactly the same<br />      dictionary. The dictionary may be fixed or may be chosen among a<br />      certain number of predefined dictionaries, according to the kind<br />      of input data. The decompressor can determine which dictionary has<br />      been chosen by the compressor by checking the dictionary<br />      identifier. This document does not specify the contents of<br />      predefined dictionaries, since the optimal dictionaries are<br />      application specific. Standard data formats using this feature of<br />      the zlib specification must precisely define the allowed<br />      dictionaries.<br /><br />   8.2. The Adler-32 algorithm<br /><br />      The Adler-32 algorithm is much faster than the CRC32 algorithm yet<br />      still provides an extremely low probability of undetected errors.<br /><br />      The modulo on unsigned long accumulators can be delayed for 5552<br />      bytes, so the modulo operation time is negligible.  If the bytes<br />      are a, b, c, the second sum is 3a + 2b + c + 3, and so is position<br />      and order sensitive, unlike the first sum, which is just a<br />      checksum.  That 65521 is prime is important to avoid a possible<br />      large class of two-byte errors that leave the check unchanged.<br />      (The Fletcher checksum uses 255, which is not prime and which also<br />      makes the Fletcher check insensitive to single byte changes 0 &lt;-&gt;<br />      255.)<br /><br />      The sum s1 is initialized to 1 instead of zero to make the length<br />      of the sequence part of s2, so that the length does not have to be<br />      checked separately. (Any sequence of zeroes has a Fletcher<br />      checksum of zero.)<br /><br />9. Appendix: Sample code<br /><br />   The following C code computes the Adler-32 checksum of a data buffer.<br />   It is written for clarity, not for speed.  The sample code is in the<br />   ANSI C programming language. Non C users may find it easier to read<br />   with these hints:<br /><br />      &amp;      Bitwise AND operator.<br />      &gt;&gt;     Bitwise right shift operator. When applied to an<br />             unsigned quantity, as here, right shift inserts zero bit(s)<br />             at the left.<br />      &lt;&lt;     Bitwise left shift operator. Left shift inserts zero<br />             bit(s) at the right.<br />      ++     "n++" increments the variable n.<br />      %      modulo operator: a % b is the remainder of a divided by b.<br /><br />      #define BASE 65521 /* largest prime smaller than 65536 */<br /><br />      /*<br />         Update a running Adler-32 checksum with the bytes buf[0..len-1]<br />       and return the updated checksum. The Adler-32 checksum should be<br />       initialized to 1.<br /><br />       Usage example:<br /><br />         unsigned long adler = 1L;<br /><br />         while (read_buffer(buffer, length) != EOF) {<br />           adler = update_adler32(adler, buffer, length);<br />         }<br />         if (adler != original_adler) error();<br />      */<br />      unsigned long update_adler32(unsigned long adler,<br />         unsigned char *buf, int len)<br />      {<br />        unsigned long s1 = adler &amp; 0xffff;<br />        unsigned long s2 = (adler &gt;&gt; 16) &amp; 0xffff;<br />        int n;<br /><br />        for (n = 0; n &lt; len; n++) {<br />          s1 = (s1 + buf[n]) % BASE;<br />          s2 = (s2 + s1)     % BASE;<br />        }<br />        return (s2 &lt;&lt; 16) + s1;<br />      }<br /><br />      /* Return the adler32 of the bytes buf[0..len-1] */<br /><br />      unsigned long adler32(unsigned char *buf, int len)<br />      {<br />        return update_adler32(1L, buf, len);<br />      }<br /><br /></pre>
		<p align="center">
				<script language="JavaScript">
						<!--
erfc("1950");
// -->
				</script>
		</p>
<img src ="http://www.cppblog.com/swo2006/aggbug/11407.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/swo2006/" target="_blank">swo</a> 2006-08-18 14:40 <a href="http://www.cppblog.com/swo2006/articles/11407.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>