LZ77文件压缩算法

LZ77压缩算法

1977由两个以色列人提出的基于重复语句层面的一种通用的压缩算法。
通用:对文件没有要求最终是将重复语句替换成更短的<长度,距离,先行缓冲区匹配字符串的下- -个字符>对,以达到压缩的目的。

mnoabczxyuvwabc 123456abczxydefgh

mnoabczxyuvw(3, 9, 1)23456(6, 18, d)efgh

找到一个重复子串后,需要将先行缓冲区匹配字符串的下一个字符按照源字符的方式写入压缩文件,下次如果匹配efg

GZIP 中的LZ77思想

GZIP: LZ77从重复语句层面压缩+ huffman从字节层面进行压缩

在ZIP算法中,也使用到LZ77的算法思想,但是对其进行了改进,主要是对于短语标记的改进:只使用“长度+距离”的二元组进行表示,匹配的查找是在查找缓冲区中进行的,即字典。
在这里插入图片描述

1、从之前压缩过的部分找重复

2、找到重复:将从夫出现的字符串使用(长度,距离)进行替换
 未找到重复:将该字节写在压缩文件中

注意:查找缓冲区的数据是已经被扫描过,建立的字典中的数据,先行缓冲区即为带压缩数据

查找缓冲区:

  1. 该部分的数据已近压缩写到压缩文件中
  2. 带压缩数据对应的-一个字符串将来要在该区域中找重复
  3. 随着压缩的进行,查找缓冲区在不断的增大

先行缓冲区:

  1. 待压缩的数据
  2. 每次从该区域中取一个字符串,然后在查找缓冲区中进行匹配
  3. 随着压缩的进行,先行缓冲区在不断的缩小

真正的数据压缩数据存储,长度,距离对不会用括号括起来,也不会用逗号隔开,因为会影响压缩比率。
那如何区分是原字符还是长度距离对呢?

采用标记位

在这里插入图片描述

重复字符串有几个时候进行长度距离对的替换?

匹配字符串的长度用一个字节存储: [0, 255]

为什么长度用一个字节表示: 一个字节可表示的最大值为255,255理论已经比较长,如果匹配长度超过255,长度必须要通过两个字节来进行存储,而正常文件中的匹配长度可能都小于255,- -个字节就可以存储,如果用两个字节存储时,对压缩率会有一定的影响。

距离用几个字节来进行存储?

就要必须知道缓冲区有多大?
缓冲区越大,查找到重复概率就更高

LZ77:缓冲区的大小—> 64K

理论上:应该在整个查找缓冲区中找匹配但是实际不会这么做:根据实际情况,重复-般都是有局部原理性-- -重复-般都不会 太远虽然在整个查找缓冲区中进行查找,找到匹配的概率会更大,但是会严重增大查找的效率为了提升一点点的压缩比率,程序效率大大牺牲真正匹配范围不会超好WSIZE: 32K---->2^5*K—> 两个字节 [1,32768]

<长度,距离对>总共占了三个字节,匹配串长度

1个字符—>肯定不会找匹配,即不会压缩

2个字符—>如果找到的匹配长度是2个字符,不会进行匹配,因为如果将2个字符用<长度,距离>对替换—>会使压缩文件变大3个字符或以上字符才开始替换。
最小匹配长度 MIN MATCH LEN = 3;
最大匹配长度 MAX_ MATCH LEN = 258;

一个字节范围[0, 255]—>0表示长度3,1表示长度4…255长度258。
如果某个匹配长度超过258,则拆成两个匹配来进行表示

如何查找最长匹配串?

1、 暴力求解

该算法的性能比较差,是一个O(N^2)的算法,如果待压缩文件比较大,
会严重影响压缩的速度。

2、 采用哈希

哈希思想查找最大匹配串

使用哈希“桶”保存每三个相邻字符构成的字符串中首字符的窗口索引。
压缩过程中每遇到新字符时,进行如下操作:

  1. 利用哈希函数计算该字符与紧跟其后的两个字符构成字符串的哈希地址
  2. 将该字符串中首字符在窗口中的索引插入上述计算出哈希位置的哈希桶中,返回插入之前该桶的状态
  3. 根据2返回的状态监测是否找到匹配串
    如果当前桶为空,说明未找到匹配,
    否则:可能找到匹配,再定位到匹配串位置详细进行匹配即可。
    利用哈希的思想,可大大提高查找匹配串的效率。

关于"哈希桶",引发出以下问题:

  1. 哈希桶的大小分析:
    三个字符总共可以组成 种取值(即16M),桶的个数需要 个,而索引大小占2个字节,总共桶占32M字节,是一个非常大的开销。随着窗口的移动,表中的数据会不断过时,维护这么大的表,会降低程序
    运行的效率。因此本文哈希桶的个数设置为: (即32K)。

哈希表的结构

现在减少为2^15个哈希桶,必然会产生哈希冲突。如果采用开散列解决,链表中的节点要不断申请与释放,而且浪费空间,影响呈现效率。

因此本文哈希表由一整块连续的内存构成,分为两个部分,每部分大小WSIZE(32K)。
如下图所示:

prev指向该字典整个内存的起始位置,head = prev + WSIZE,内存是连续的,所以prev和head可以看作两个数组,即prev[]和head[]。

head数组用来保存三个字符串首字符的索引位置,head的索引为三个字符通过哈希函数计算的哈希值。而prev就是来解决冲突的。

在这里插入图片描述
abc---->计算出的哈希地址5,abc在原缓冲区中的下标3
即: head[5] = 3

第二个abc向哈希表中的插入过程:

abc–>哈希函数—>哈希地址: 5, abc在原缓冲区中的下标12此时:
发生哈希冲突

解决: prev的空间专门 ]是用来解决哈希冲突

常规哈希表中如果要进行查找,表格中的元素已经全部放进去了,
再来进查找。

哈希表: 一边向哈希表中插入内容,一边查找。

查找匹配过程:假设拿到的是abc
计算哈希地址hashAddr=3;

第一次匹配: matchHead =_ head[hashAddr];
matchHead:19进行一 次匹配,本次匹配不一定是最长的,继续匹配

第二次匹配: matchHead =_ prev[matchHead];
matchHead:10进行一次匹配, 本次匹配不- -定是 最长的,继续匹配

第三次匹配: matchHead =_ prev[matchHead];
matchHead:1进行一 次匹配,本次匹配不一定是最长的,继续匹配

第四次匹配: matchHead =_ prev[matchHead]; matchHead:0说明没有相同字符串了,不匹配

随着压缩的进行,start(表示当前 压缩到的位置) start会 大于WSIZE .

进而引出新的问题
向Hash表中插入字符的过程

pos:代表abc首字符a在缓冲区中的下标(即: start)
prev[pos] = head[hashAddr];
_head[hashAddr] = pos;
prev的大小32K–>WSIZE
一但pos > WSIZE
_prev[pos] 就越界了

当pos超过WSIZE时,在插入函数中如果直接使用pos肯定会越界,因此需要与上WMASK,即
_prev[pos & WMASK] = _head[hashAddr]

但是该语句可能会破坏匹配链,让匹配链构成环而造成死循环,该情况如何处理?
解决方式,匹配次数最多匹配255次。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值