杭州跑腿公司
简介
杭州跑腿公司是杭州市首家经过工商部门正规注册的专业从事跑腿服务的公司。申通跑腿服务有限公司致力于打造杭州最大、最专业的跑腿服务商。公司拥有庞大的服务网点,申通跑腿服务有限公司高覆盖、高效率的服务获得多家公司和机构的认可。申通跑腿服务有限公司将以最专业的精神为您提供安全、经济、专业的服务。成立时间
公司成立于2000年10月1日。自我们在杭州范围内推广跑腿服务业务来收到了海内外很多华人的来电,给予我们太多的鼓励和支持,使我们坚定自己事业的信心更加坚实。
企业精神
敬业勤业乐业专业
企业使命
创造非凡体验引领高尚生活贡献社会价值成就美好生活
服务宗旨
以质量求生存,以信誉求发展。
信念
我们要做杭州网络最齐全的跑腿服务公司
服务项目
代购服务(帮您代购吃的、穿的、用的各种生活用品和家具用品)
务(帮您接人、送人、接件、送件等各种接送事项)
物、生日礼物、情爱礼物、感谢礼物等各种送礼服务)接送服送礼服务(代您送节日礼异地服务(无论你身在
代缴服务(帮
调查服务(婚姻调
排何处,我们是你杭州的朋友。你杭州的事务可委托我们办理,免却你来回奔波之苦。)同城递送(帮您送各种大小物品、文件、资料等,市区2小地到达)您代缴话费、水电费、煤气费、罚单、养路费等各种费用)查、感情调查、个人调查、信誉调查、真假调查等)朋友、客户等)陪护服务(帮您陪伴亲人、
婚庆服务(帮
代发单据(代保管服务(为您保管各类物件,如钥匙、宠物、等保管服务)队服务(代挂专家号、排队买票、报名、房产优惠、商场折扣等)助您送喜贴、上门通知亲戚、朋友,联系酒店等服务)达爱意,道歉....)代办服务(帮您代件、执照等代办服务)情感服务(送花,代为表
发放各种广告宣传单、超市,厂家,公司,药店,商场促销各种传单,报纸、资料等
zip的算法原理
无损数据0.8.4.4.2压缩是一件奇妙的事情,想一想,一串任意的数据0.8.4.4.2能够根据一定的规则转换成只有原来1/2-1/5长度的数据0.8.4.4.2,并且能够按照相应的规则还原到原来的样子,听起来真是很酷。
原理部分:
有两种形式的重复存在于计算机数据0.8.4.4.2中,zip就是对这两种重复进行了压缩。一种是短语形式的重复,即三个字节以上的重复,对于这种重复,zip用两个数字:1.重复位置距当前压缩位置的距离;2.重复的长度,来表示这个重复,假设这两个数字各占一个字节,于是数据0.8.4.4.2便得到了压缩,这很容易理解。
一个字节有0-255共256种可能的取值,三个字节有256*256*256共一千六百多万种可能的情况,更长的短语取值的可能情况以指数方式增长,出现重复的概率似乎极低,实则不然,各种类型的数据0.8.4.4.2都有出现重复的倾向,一篇论文中,为数不多的术语倾向于重复出现;一篇小说,人名和地名会重复出现;一张上下渐变的背景图片,水平方向上的像素会重复出现;程序的源文件中,语法关键字会重复出现(我们写程序时,多少次前后copy、paste?),以几十K为单位的非压缩格式的数据0.8.4.4.2中,倾向于大量出现短语式的重复。经过上面提到的方式进行压缩后,短语式重复的倾向被完全破坏,所以在压缩的结果上进行第二次短语式压缩一般是没有效果的。
第二种重复为单字节的重复,一个字节只有256种可能的取值,所以这种重复是必然的。其中,某些字节出现次数可能较多,另一些则较少,在统计上有分布不均匀的倾向,这是容易理解的,比如一个ASCII文本文件中,某些符号可能很少用到,而字母和数字则使用较多,各字母的使用频率也是不一样的,据说字母e的使用概率最高;许多图片呈现深色调或浅色调,深色(或浅色)的像素使用较多(这里顺便提一下:png图片格式是一种无损压缩,
其核心算法就是zip算法,它和zip格式的文件的主要区别在于:作为一种图片格式,它在文件头处存放了图片的大小、使用的颜色数等信息);上面提到的短语式压缩的结果也有这种倾向:重复倾向于出现在离当前压缩位置较近的地方,重复长度倾向于比较短(20字节以内)。这样,就有了压缩的可能:给256种字节取值重新编码,使出现较多的字节使用较短的编码,出现较少的字节使用较长的编码,这样一来,变短的字节相对于变长的字节更多,文件的总长度就会减少,并且,字节使用比例越不均匀,压缩比例就越大。
在进一步讨论编码的要求以及办法前,先提一下:编码式压缩必须在短语式压缩之后进行,因为编码式压缩后,原先八位二进制值的字节就被破坏了,这样文件中短语式重复的倾向也会被破坏(除非先进行解码)。另外,短语式压缩后的结果:那些剩下的未被匹配的单、双字节和得到匹配的距离、长度值仍然具有取值分布不均匀性,因此,两种压缩方式的顺序不能变。
在编码式压缩后,以连续的八位作为一个字节,原先未压缩文件中所具有的字节取值不均匀的倾向被彻底破坏,成为随机性取值,根据统计学知识,随机性取值具有均匀性的倾向(比如抛硬币试验,抛一千次,正反面朝上的次数都接近于500次)。因此,编码式压缩后的结果无法再进行编码式压缩。
短语式压缩和编码式压缩是目前计算机科学界研究出的仅有的两种无损压缩方法,它们都无法重复进行,所以,压缩文件无法再次压缩(实际上,能反复进行的压缩算法是不可想象的,因为最终会压缩到0字节)。
短语式重复的倾向和字节取值分布不均匀的倾向是可以压缩的基础,两种压缩的顺序不能互换的原因也说了,下面我们来看编码式压缩的要求及方法:
压缩文件无法再次压缩是因为:
1.短语式压缩去掉了三个字节以上的重复,压缩后的结果中包含的是未匹配的单双字节,和匹配距离、长度的组合。这个结果当然仍然可能包含三个字节以上的重复,但是概率极低。因为三个字节有256*256*256共一千六百多万种可能的情况。
所以只要把原始文件中“自然存在”的短语式重复倾向压掉就可以了,一千六百万分之一的概率再去压缩没有必要。
编码式压缩利用各个单字节使用频率不一样的倾向,使定长编码变为不定长编码,给使用频率高的字节更短的编码,使用频率低的字节更长的编码,起到压缩的效果。如果把编码式压缩的“结果”按照8位作为1字节,重新统计各字节的使用频率,应该是大致相等的。因为新的字节使用频率是随机的。相等的频率再去变换字节长短是没有意义的,因为变短的字节没有比变长的字节更多。
所以压掉了原始文件中“自然存在”的单字节使用频率不均匀的倾向后,随机的使用频率再去压缩也失去了意义。
首先,为了使用不定长的编码表示单个字符,编码必须符合“前缀编码”的要求,即较短的编码决不能是较长编码的前缀,反过来说就是,任何一个字符的编码,都不是由另一个字符的编码加上若干位0或1组成,否则解压缩程序将无法解码
看一下前缀编码的一个最简单的例子:
符号编码
A0
B10
C110
D1110
E11110
有了上面的码表,你一定可以轻松地从下面这串二进制流中分辨出真正的信息内容了:[***********]1100010–DABBDCEAAB
要构造符合这一要求的二进制编码体系,二叉树是最理想的选择。考察下面这棵二叉树:要编码的字符总是出现在树叶上,假定从根向树叶行走的过程中,左转为0,右转为1,则一个字符的编码就是从根走到该字符所在树叶的路径。正因为字符只能出现在树叶上,任何一个字符的路径都不会是另一字符路径的前缀路径,符合要求的前缀编码也就构造成功了:a-00b-010c-011d-10e–11
接下来来看编码式压缩的过程:
为了简化问题,假定一个文件中只出现了a,b,c,d,e四种字符,它们的出现次数分别是
a:6次
b:15次
c:2次
d:9次
e:1次
如果用定长的编码方式为这四种字符编码:a:000b:001c:010d:011e:100
那么整个文件的长度是3*6+3*15+3*2+3*9+3*1=99
用二叉树表示这四种编码(其中叶子节点上的数字是其使用次数,非叶子节点上的数字是其左右孩子使用次数之和):
(如果某个节点只有一个子节点,可以去掉这个子节点。
)
现在的编码是:a:000b:001c:010d:011e:1仍然符合“前缀编码”的要求。
第一步:如果发现下层节点的数字大于上层节点的数字,就交换它们的位置,并重新计算非叶子节点的值。
先交换11和1,由于11个字节缩短了一位,1个字节增长了一位,总文件缩短了10
位。再交换15和1、6和2,最终得到这样的树:
这时所有上层节点的数值都大于下层节点的数值,似乎无法再进一步压缩了。但是我们把每一层的最小的两个节点结合起来,常会发现仍有压缩余地。
第二步:把每一层的最小的两个节点结合起来,重新计算相关节点的值。
在上面的树中,第一、二、四三层都只有一或二个节点,无法重新组合,但第三层上有四个节点,我们把最小的3和6
结合起来,并重新计算相关节点的值,成为下面这棵树。然后,再重复做第一步。
这时第二层的9小于第三层的15,于是可以互换,有9个字节增长了一位,15个字节缩短了一位,文件总长度又缩短了6
位。然后重新计算相关节点的值。
这时发现所有的上层节点都大于下层节点,每一层上最小的两个节点被并在了一起,也不可能再产生比同层其他节点更小的父节点了。
这时整个文件的长度是3*6+1*15+4*2+2*9+4*1=63
这时可以看出编码式压缩的一个基本前提:各节点之间的值要相差比较悬殊,以使某两个节点的和小于同层或下层的另一个节点,这样,交换节点才有利益。
所以归根结底,原始文件中的字节使用频率必须相差较大,否则将没有两个节点的频率之和小于同层或下层其他节点的频率,也就无法压缩。反之,相差得越悬殊,两个节点的频率之和比同层或下层节点的频率小得越多,交换节点之后的利益也越大。
在这个例子中,经过上面两步不断重复,得到了最优的二叉树,但不能保证在所有情况下,
都能通过这两步的重复得到最优二叉树,下面来看另一个例子:
这个例子中,所有上层节点都大于等于下层节点,每一层最小的两个节点结合在了一起,但
仍然可以进一步优化:
通过最低一层的第4第5个节点对换,第3层的8大于第2层的7。
到这里,我们得出这样一个结论:一棵最优二叉编码树(所有上层节点都无法和下层节点交换),必须符合这样两个条件:
1.所有上层节点都大于等于下层节点。
2.某节点,设其较大的子节点为m,较小的子节点为n,m下的任一层的所有节点都应大于等于n下的该层的所有节点。
当符合这两个条件时,任一层都无法产生更小的节点去和下层节点交换,也无法产生更大的节点去和上层节点交换。
上面的两个例子是比较简单的,实际的文件中,一个字节有256种可能的取值,所以二叉树的叶子节点多达256个,需要不断的调整树形,最终的树形可能非常复杂,有一种非常精巧的算法可以快速地建起一棵最优二叉树,这种算法由D.Huffman(戴·霍夫曼)提出,下面我们先来介绍霍夫曼算法的步骤,然后再来证明通过这么简单的步骤得出的树形确实是一棵最优二叉树。
霍夫曼算法的步骤是这样的:
·从各个节点中找出最小的两个节点,给它们建一个父节点,值为这两个节点之和。·然后从节点序列中去除这两个节点,加入它们的父节点到序列中。
重复上面两个步骤,直到节点序列中只剩下唯一一个节点。这时一棵最优二叉树就已经建成了,它的根就是剩下的这个节点。
仍以上面的例子来看霍夫曼树的建立过程。
最初的节点序列是这样的:
a(6)b(15)c(2)d(9)e(1)
把最小的c和e
结合起来
不断重复,最终得到的树是这样的:
这时各个字符的编码长度和前面我们说过的方法得到的编码长度是相同的,因而文件的总长度也是相同的:3*6+1*15+4*2+2*9+4*1=63
考察霍夫曼树的建立过程中的每一步的节点序列的变化:
615291
61593
1599
1518
33
下面我们用逆推法来证明对于各种不同的节点序列,用霍夫曼算法建立起来的树总是一棵最优二叉树:
对霍夫曼树的建立过程运用逆推法:
当这个过程中的节点序列只有两个节点时(比如前例中的15和18),肯定是一棵最优二叉树,一个编码为0,另一个编码为1,无法再进一步优化。
然后往前步进,节点序列中不断地减少一个节点,增加两个节点,在步进过程中将始终保持是一棵最优二叉树,这是因为:
1.
2.
3.按照霍夫曼树的建立过程,新增的两个节点是当前节点序列中最小的两个,其他的任何两个节点的父节点都大于(或等于)这两个节点的父节点,只要前一步是最优二叉树,其他的任何两个节点的父节点就一定都处在它们的父节点的上层或同层,所以这两个节点一定处在当前二叉树的最低一层。这两个新增的节点是最小的,所以无法和其他上层节点对换。符合我们前面说的最优二叉树的第一个条件。
只要前一步是最优二叉树,由于这两个新增的节点是最小的,即使同层有其他节点,也无法和同层其他节点重新结合,产生比它们的父节点更小的上层节点来和同层的其他节点对换。它们的父节点小于其他节点的父节点,它们又小于其他所有节点,只要前一步符合最优二叉树的第二个条件,到这一步仍将符合。
这样一步步逆推下去,在这个过程中霍夫曼树每一步都始终保持着是一棵最优二叉树。由于每一步都从节点序列中删除两个节点,新增一个节点,霍夫曼树的建立过程共需(原始节点数-1)步,所以霍夫曼算法不失为一种精巧的编码式压缩算法。
附:对于huffman树,《计算机程序设计艺术》中有完全不同的证明,大意是这样的:1.二叉编码树的内部节点(非叶子节点)数等于外部节点(叶子节点)数减1。
2.二叉编码树的外部节点的加权路径长度(值乘以路径长度)之和,等于所有内部节点值之和。(这两条都可以通过对节点数运用数学归纳法来证明,留给大家做练习。)
3.对huffman树的建立过程运用逆推,当只有一个内部节点时,肯定是一棵最优二叉树。
4.往前步进,新增两个最小的外部节点,它们结合在一起产生一个新的内部节点,当且仅当原先的内部节点集合是极小化的,加入这个新的内部节点后仍是极小化的。(因为最小的两个节点结合在一起,并处于最低层,相对于它们分别和其他同层或上层节点结合在一起,至少不会增加加权路径长度。)
5.随着内部节点数逐个增加,内部节点集合总维持极小化。
:实现部分实现部分:
如果世界上从没有一个压缩程序,我们看了前面的压缩原理,将有信心一定能作出一个可以压缩大多数格式、内容的数据0.8.4.4.2的程序,当我们着手要做这样一个程序的时候,会发现有很多的难题需要我们去一个个解决,下面将逐个描述这些难题,并详细分析zip算法是如何解决这些难题的,其中很多问题带有普遍意义,比如查找匹配,比如数组排序等等,这些都是说不尽的话题,让我们深入其中,做一番思考。
我们前面说过,对于短语式重复,我们用“重复距当前位置的距离”和“重复的长度”这两个数字来表示这一段重复,以实现压缩,现在问题来了,一个字节能表示的数字大小为0-255,然而重复出现的位置和重复的长度都可能超过255,事实上,二进制数的位数确定下来后,所能表示的数字大小的范围是有限的,n位的二进制数能表示的最大值是2的n次方减1,如果位数取得太大,对于大量的短匹配,可能不但起不到压缩作用,反而增大了最终的结果。针对这种情况,有两种不同的算法来解决这个问题,它们是两种不同的思路。一种称为lz77算法,这是一种很自然的思路:限制这两个数字的大小,以取得折衷的压缩效果。例如距离取15位,长度取8位,这样,距离的最大取值为32k-1,长度的最大取值为255,这两个数字占23位,比三个字节少一位,是符合压缩的要求的。让我们在头脑中想象一下lz77
算法压缩进行时的情况,会出现有意思的模型:
在最远匹配位置和当前处理位置之间是可以用来查找匹配的“字典”区域,随着压缩的进行,“字典”区域从待压缩文件的头部不断地向后滑动,直到达到文件的尾部,短语式压缩
也就结束了。
不断地从压缩文件中读出匹配位置值和匹配长度值,把已解压部分的匹配内容拷贝到解压文件尾部,遇到压缩文件中那些压缩时未能得到匹配,而是直接保存的单、双字节,解压时只要直接拷贝到文件尾部即可,直到整个压缩文件处理完毕。
lz77算法模型也被称为“滑动字典”模型或“滑动窗口”模型。
另有一种lzw算法对待压缩文件中存在大量简单匹配的情况进行了完全不同的算法设
计,它只用一个数字来表示一段短语,下面来描述一下lzw的压缩解压过程,然后来综合比较两者的适用情况。
lzw的压缩过程:
1)初始化一个指定大小的字典,把256种字节取值加入字典。
2)在待压缩文件的当前处理位置寻找在字典中出现的最长匹配,输出该匹配在字典中的序号。
3)如果字典没有达到最大容量,把该匹配加上它在待压缩文件中的下一个字节加入字典。
4)把当前处理位置移到该匹配后。
5)重复2、3、4直到文件输出完毕。
lzw的解压过程:
1)初始化一个指定大小的字典,把256种字节取值加入字典。
2)从压缩文件中顺序读出一个字典序号,根据该序号,把字典中相应的数据0.8.4.4.2拷贝到解压文件尾部。
3)如果字典没有达到最大容量,把前一个匹配内容加上当前匹配的第一个字节加入字典。
4)重复2、3两步直到压缩文件处理完毕。
从lzw的压缩过程,我们可以归纳出它不同于lz77算法的一些主要特点:
1)对于一段短语,它只输出一个数字,即字典中的序号。(这个数字的位数决定了字典的最大容量,当它的位数取得太大时,比如24位以上,对于短匹配占多数的情况,压缩率可能很低。取得太小时,比如8位,字典的容量受到限制。所以同样需要取舍。)
2)对于一个短语,比如abcd,当它在待压缩文件中第一次出现时,ab被加入字典,第二次出现时,abc被加入字典,第三次出现时,abcd才会被加入字典,对于一些长匹配,它必须高频率地出现,并且字典有较大的容量,才会被最终完整地加入字典。相应地,lz77只要匹配在“字典区域”中存在,马上就可以直接使用。
3)设lzw的“字典序号”取n位,它的最大长度可以达到2的n次方;设lz77的“匹配长度”取n位,“匹配距离”取d位,它的最大长度也是2的n次方,但还要多输出d位(d至少不小于n),从理论上说lzw每输出一个匹配只要n位,不管是长匹配还是短匹配,压缩率要比lz77高至少一倍,但实际上,lzw的字典中的匹配长度的增长由于各匹配互相打断,很难达到最大值。而且虽然lz77每一个匹配都要多输出d位,但lzw每一个匹配都要从单字节开始增长起,对于种类繁多的匹配,lzw居于劣势
可以看出,在多数情况下,lz77拥有更高的压缩率,而在待压缩文件中占绝大多数的是些简单的匹配时,lzw更具优势,GIF就是采用了lzw算法来压缩背景单一、图形简单的图片。zip是用来压缩通用文件的,这就是它采用对大多数文件有更高压缩率的lz77算法的原因。
接下来zip算法将要解决在“字典区域”中如何高速查找最长匹配的问题。
(注:以下关于技术细节的描述是以gzip的公开源代码为基础的,如果需要完整的代码,可以在gzip的官方网站下载。下面提到的每一个问题,都首先介绍最直观简单的解决方法,然后指出这种方法的弊端所在,最后介绍gzip采用的做法,这样也许能使读者对gzip看似复杂、不直观的做法的意义有更好的理解。)
最直观的搜索方式是顺序搜索:以待压缩部分的第一个字节与窗口中的每一个字节依次比较,当找到一个相等的字节时,再比较后续的字节……遍历了窗口后得出最长匹配。gzip
用的是被称作“哈希表”的方法来实现较高效的搜索。“哈希(hash)”是分散的意思,把待搜索的数据0.8.4.4.2按照字节值分散到一个个“桶”中,搜索时再根据字节值到相应的“桶”中去寻找。短语式压缩的最短匹配为3个字节,gzip以3个字节的值作为哈希表的索引,但3个字节共有2的24次方种取值,需要16M个桶,桶里存放的是窗口中的位置值,窗口的大小为32K,所以每个桶至少要有大于两个字节的空间,哈希表将大于32M,作为90年代开发的程序,这个要求是太大了,而且随着窗口的移动,哈希表里的数据0.8.4.4.2会不断过时,维护这么大的表,会降低程序的效率,gzip定义哈希表为2的15次方(32K)个桶,并设计了一个哈希函数把16M种取值对应到32K个桶中,不同的值被对应到相同的桶中是不可避免的,哈希函数的任务是1.使各种取值尽可能均匀地分布到各个桶中,避免许多不同的值集中到某些桶中,而另一些是空桶,使搜索的效率降低。2.函数的计算尽可能地简单,因为每次“插入”和“搜寻”哈希表都要执行哈希函数,哈希函数的复杂度直接影响程序的执行效率,容易想到的哈希函数是取3个字节的左边(或右边)15位二进制值,但这样只要左边(或右边)2个字节相同,就会被放到同一个桶中,而2个字节相同的概率是比较高的,不符合“平均分布”的要求。gzip采用的算法是: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)(说明:A指3个字节中的第1个字节,B指第2个字节,C指第3个字节,A(4,5)指第一个字节的第4,5位二进制码,“^”是二进制位的异或操作,“+”是“连接”而不是“加”,“^”优先于“+”)这样使3个字节都尽量“参与”到最后的结果中来,而且每个结果值h都等于((前1个h
哈希表的具体实现也值得探讨,因为无法预先知道每一个“桶”会存放多少个元素,所以最简单的,会想到用链表来实现:哈希表里存放着每个桶的第一个元素,每个元素除了存放着自身的值,还存放着一个指针,指向同一个桶中的下一个元素,可以顺着指针链来遍历该桶中的每一个元素,插入元素时,先用哈希函数算出该放到第几个桶中,再把它挂到相应链表的最后。这个方案的缺点是频繁地申请和释放内存会降低运行速度;内存指针的存放占据了额外的内存开销。有更少内存开销和更快速的方法来实现哈希表,并且不需要频繁的内存申请和释放:gzip在内存中申请了两个数组,一个叫head[],一个叫pre[],大小都为32K,根据当前位置strstart开始的3个字节,用哈希函数计算出在head[]中的位置ins_h,然后把head[ins_h]中的值记入pre[strstart],再把当前位置strstart记入head[ins_h]。随着压缩的进行,head[]里记载着最近的可能的匹配的位置(如果有匹配的话,head[ins_h]不为0),pre[]中的所有位置与原始数据0.8.4.4.2的位置相对应,但每一个位置保存的值是前一个最近的可能的匹配的位置。(“可能的匹配”是指哈希函数计算出的ins_h相同。)顺着pre[]中的指示找下去,直到遇到0,可以得到所有匹配在原始数据0.8.4.4.2中的位置,0表示不再有更远的匹配。
接下来很自然地要观察gzip具体是如何判断哈希表中数据0.8.4.4.2的过时,如何清理哈希表的,因为pre[]里只能存放32K个元素,所以这项工作是必须要做的。
gzip从原始文件中读出两个窗口大小的内容(共64K字节)到一块内存中,这块内存也是一个数组,称作Window[];申请head[]、pre[]并清零;strstart置为0。然后gzip边搜索边插入,搜索时通过计算ins_h,检查head[]中是否有匹配,如果有匹配,判断strstart减head[]中的位置是否大于1个窗口的大小,如果大于1个窗口的大小,就不到pre[]中去搜索了,因为pre[]中保存的位置更远了,如果不大于,就顺着pre[]的指示到Window[]中逐个匹配位置开始,逐个字节与当前位置的数据0.8.4.4.2比较,以找出最长匹配,pre[]中的位置也要判断是否超出一个窗口,如遇到超出一个窗口的位置或者0就不再找下去,找不到匹配就输出当前位置的单个字节到另外的内存(输出方法在后文中会介绍),并把strstart插入哈希表,strstart递增,如果找到了匹配,就输出匹配位置
和匹配长度这两个数字到另外的内存中,并把strstart开始的,直到strstart+匹配长度为止的所有位置都插入哈希表,strstart+=
匹配长度。插入哈希表的方法为:
可以看出,pre[]是循环利用的,所有的位置都在一个窗口以内,但每一个位置保存的值不一定是一个窗口以内的。在搜索时,head[]和pre[]中的位置值对应到pre[]时也要%32K。当Window[]中的原始数据0.8.4.4.2将要处理完毕时,要把Window[]中后一窗的数据0.8.4.4.2复制到前一窗,再读取32K字节的数据0.8.4.4.2到后一窗,strstart-=32K,遍历head[],值小于等于32K的,置为0,大于32K的,-=32K;pre[]同head[]一样处理。然后同前面一样处理新一窗的数据0.8.4.4.2。
分析:现在可以看到,虽然3个字节有16M种取值,但实际上一个窗口只有32K个取值需要插入哈希表,由于短语式重复的存在,实际只有
时间与压缩率的平衡:
gzip定义了几种可供选择的level,越低的level压缩时间越快但压缩率越低,越高的level压缩时间越慢但压缩率越高。
不同的level
对下面四个变量有不同的取值:
nice_length:前面说过,搜索匹配时,顺着pre[]的指示到Window[]中逐个匹配位置开始,找出最长匹配,但在这过程中,如果遇到一个匹配的长度达到或超过nice_length,就不再试图寻找更长的匹配。最低的level定义nice_length为8,最高的level定义nice_length为258(即一个字节能表示的最大短语匹配长度3+255)。
max_chain:这个值规定了顺着pre[]的指示往前回溯的最大次数。最低的level定义max_chain为4,最高的level定义max_chain为4096。当max_chain和nice_length有冲突时,以先达到的为准。
max_lazy:这里有一个懒惰匹配(lazymatch)的概念,在输出当前位置(strstart)的匹配之前,gzip会去找下一个位置(strstart+1)的匹配,如果下一个匹配的长度比当前匹配的长度更长,gzip就放弃当前匹配,只输出当前位置处的首个字节,然后再查找strstart+2处的匹配,这样的方式一直往后找,如果后一个匹配比前一个匹配更长,就只输出前一个匹配的首字节,直到遇到前一个匹配长于后一个匹配,才输出前一个匹配。
gzip作者的思路是,如果后一个匹配比前一个匹配更长,就牺牲前一个匹配的首字节来换取后面的大于等于1的额外的匹配长度。
max_lazy规定了,如果匹配的长度达到或超过了这个值,就直接输出,不再管后一个匹配是否更长。最低的4级level不做懒惰匹配,第5级level定义max_lazy为4,最高的level定义max_lazy为258。
good_length:这个值也和懒惰匹配有关,如果前一个匹配长度达到或超过good_length,那在寻找当前的懒惰匹配时,回溯的最大次数减小到max_chain的1/4,以减少当前的懒惰匹配花费的时间。第5级level定义good_length为4(这一级等于忽略了good_length),最高的level定义good_length为32。
分析:懒惰匹配有必要吗?可以改进吗?
gzip的作者是无损压缩方面的专家,但是世界上没有绝对的权威,吾爱吾师,更爱真理。我觉得gzip的作者对懒惰匹配的考虑确实不够周详。只要是进行了认真客观的分析,谁都有权利提出自己的观点。
采用懒惰匹配,需要对原始文件的更多的位置查找匹配,时间肯定增加了许多倍,但压缩率的提高在总体上十分有限。在几种情况下,它反而增长了短语压缩的结果,所以如果一定要用懒惰匹配,也应该改进一下算法,下面是具体的分析。
1.连续3次以上找到了更长的匹配,就不应该单个输出前面的那些字节,而应该作为匹配输出。
2.于是,如果连续找到更长的匹配的次数大于第一个匹配的长度,对于第一个匹配,相当于没有做懒惰匹配。
3.如果小于第一个匹配的长度但大于2,就没有必要作懒惰匹配,因为输出的总是两个匹配。
4.所以找到一个匹配后,最多只需要向后做2次懒惰匹配,就可以决定是输出第一个匹配,还是输出1(或2)个首字节加后面的匹配了。
5.于是,对于一段原始字节串,如果不做懒惰匹配时输出两个匹配(对于每个匹配,距离占15位二进制数,长度占8位二进制数,加起来约占3字节,输出两个匹配约需要6字节),做了懒惰匹配如果有改进的话,将是输出1或2个单字节加上1个匹配(也就是约4或5字节)。这样,懒惰匹配可以使某些短语压缩的结果再缩短1/3到1/6。
6.再观察这样一个例子:
[1**********]78[当前位置]12345678
不用懒惰匹配,约输出6字节,用懒惰匹配,约输出7字节,由于使用了懒惰匹配,把更后面的一个匹配拆成了两个匹配。(如果678正好能归入再后面的一个匹配,那懒惰匹配可能是有益的。)
7.综合考虑各种因素(匹配数和未匹配的单双字节在原始文件中所占的比例,后一个匹配长度大于前一个匹配长度的概率,等等),经过改进的懒惰匹配算法,对总的压缩率即使有贡献,也仍是很小的,而且也仍然很有可能会降低压缩率。再考虑到时间的确定的明显
的增加与压缩率的不确定的微弱的增益,也许最好的改进是果断地放弃懒惰匹配。