概述
lz4属于lz77系列的压缩算法,lz77系列压缩算法将重复的字符串(也称为匹配)表示成(offset、match length)来对数据进行压缩。lz77算法只是一种思想,寻找匹配的方式有很多中,常见的有hash chain、BST,这些算法会在介绍各个通用压缩算法的时候介绍。
本文只介绍lz4的压缩格式,详细的算法实现会放在另外文章里面讲。
lz4实现了两种格式,分别叫block、frame
lz4_Block格式
名词翻译说明:
- sequence: 序列,lz4的block格式只包含一系列的序列,不像Frame格式会包含数据头
|sequence|sequence|sequence|sequence|sequence|…| - offset,lz4的offset由2个字节组成,0没有使用,属于非法值
- literal,未匹配的字符串
- match,匹配的字符串,即能在前面找到一样的字符串
序列的格式如下:
高4bit | 低4bit | 0-n byte | 0-n byte | 2 byte | 0-n byte |
未匹配的字符长度 | 匹配的字符长度 | 可选的未匹配的字符长度 | 未匹配的字符 | offset | 可选的匹配长度 |
序列的填值规则如下:
1.“高4bit的值”等于15,则说明“可选的未匹配的字符长度”里面有值,小于15则“可选的未匹配的字符长度”字段不存在。
“可选的未匹配的字符长度”部分,如果前一个字节的值为255,表示后面一个字节也是属于该字段。
2.“低4bit值”的存储规则与“高4bit”一致。如果值等于15,则说明“可选的匹配长度”部分还有数据与“低4bit”一起组成匹配的字符长度。
3.“offset”字段为固定的2个字节,所以lz4选取的查询窗口为固定的64KB,无法配置。该字段采用小端格式存储数值。最后一个序列只包含未匹配的字符串,为了与其他序列进行区分,offset的值是0
一般匹配的字符长度都比较小,“低4bit”就存储完整的长度值,所以lz4将第一个字节拆成高低4位,最大限度的节约了数据的存储,提高了压缩率。
下面以一个具体的例子来说明lz4_Block格式。
比如输入数据为:
abcdefghijklmnoabcdefghijklmno
lz4算法会将该字符串转成三元组。
(‘abcdefghijklmno’(未匹配的字符串),15(offset),15(match length))
转成字节流如下:
高4bit | 低4bit | 1 byte | 15 byte | 2 byte | 1 byte |
15 | 15 | 0 | abcdefghijklmno | 15 | 0 |
从上面可以看到,lz4_block格式没有header字段,一般的压缩算法有许多参数可选,比如窗口大小,挡位,是否进行crc校验等。当这些参数在解压的时候需要知道,则压缩格式中会设计header部分,并将这些数据存储进去,供解压的时候使用。
int decode(char* input, int inLen, char* output, int outLen) {
unsigned char* inp = (unsigned char*)input;
unsigned char* outp = (unsigned char*)output;
int decodeSize = 0;
while (1) {
unsigned char token = *inp;
inp++;
int high4Bits = (token & 0xF0) >> 4;
int low4Bits = token & 0x0F;
int literalLen = high4Bits;
int matchLen = 4 + low4Bits;
// 解码literalLen
if (high4Bits == 15) {
while (*inp == 255) {
literalLen += 255;
inp++;
}
literalLen += *inp;
inp++;
}
decodeSize += literalLen;
memcpy(outp, inp, literalLen);
inp += literalLen;
outp += literalLen;
//解码offset
int offset = *(inp + 1);
offset = (offset << 8) + *inp;
inp += 2;
// 解码matchLen
if (offset > 0) {
if (low4Bits == 15) {
while (*inp == 255) {
matchLen += 255;
inp++;
}
matchLen += *inp;
inp++;
}
unsigned char* matchPos = (unsigned char*)(output + decodeSize) - offset;
for (int i = 0; i < matchLen; i++) {
outp[i] = matchPos[i];
}
decodeSize += matchLen;
outp += matchLen;
}
if (inp - (unsigned char*)input >= inLen) {
break;
}
}
return decodeSize;
}