lzma算法分析

lzma算法分析
这几天在公司主要在做压缩相关,记录一下所得。
目前业界主流的压缩算法感觉并不多,好用的就Huffman,lz系列,其他的像差分编码,vlq编码,感觉只能做个数据预处理,或者一种小范围的压缩。
lz系列有很多,主要有lz77 lz78 lzma,基本思想是一样的,都是一种字典编码,如,我有一段文本,里面有“abcdefgabcde”,那么后面的abcde并没有必要,可以用前面的替代,所以,其实可存储为“abcdefg65”,6代表offset,5代表length,既用距离当前位置6字节,长度为5的字节串表示当前字节串,由此就减少了三个字节,当然,这里要求0和5也要是u8类型,或者更小。
lz系列核心算法其实很简单,就是这样,但却是业界压缩效果最好的算法,大道至简啊。
lzma就是这么做的,在这里,思考这么几个问题:
1:当压缩算法扫描到中间某段位置时,如何和前面的内容进行快速比较。
2:如何区分当前是压缩的内容,还是未压缩的内容。
首先回答第一个问题,很简单,用哈希,笔者采用了FNV哈希算法,效果挺好的,用c++stl中的hash不知道效果如何,不过其实后来发现,哈希冲突在这里并不严重,一个好的哈希只能带来速度和内存的提升了,没办法提升压缩率的。然后我们如何解决哈希冲突呢,hash冲突这里其实是重点,所表达的就是可能出现了相同字节串。
我们先说这个算法如何工作,首先我们只对定长的几个字节算hash值,例如我们只算5个字节的hash值,然后保存hash,之后每扫描一个字节,取出当前位置往后5个字节,算hash,和前面的比较,如果hash值相同,则在进一步比较每个字节内容,hash可以很快的知道内容是不一样的,没有办法知道内容是一样的,所以我们还需要实际的去比较每个字节,这意味着,我们在保存hash的时候,还需要保存算得这个hash的字节在整个文件中的index,我们取出上一个相同hash值的字节的起始index,和当前位置往后比,一直比到文件结束或着出现不同,这里是一个重点,如:abcdefgabcabcabcabc,想象一下,这里如何压缩呢,好的做法是:abcdefg3339,第一组33代表offet是3,长度是3,第二个39offet是3(就是第一个03),长度是9,所以,相同字节串可以涵盖本身(39包含了自己)。
回到前面,我们应该如何设计保存hash的结构了,首先为了快速索引,应该用一个数组或者vector保存hash,既hash值作为下标,这样可以快速索引,如何解决hash冲突呢,easy,不解决,只保存,如我们构造一个长度为1000的数组,把数组分成100块,每块有10个元素,产生的hash值在0-99中间,现在算的hash值为1,那么在第一块的第一个位置填入此时的index,往后又算出hash值1时,则在第1块的第二个位置填入,所以,这里的块数作为hash索引, 一块里面有多少个则代表可填入相同hash值的数量,所以如果数量很多,那么就覆盖前面的,这里可以用一个环形缓冲区实现。
回答第二个问题,如何分别是原来未压缩的数据,还是保存的offset和length呢,用VLQ编码,此处请goolge vlq编码,此外在实际验证中发现,offset 和length采用vlq编码后,往往length比较小,采用vlq至少需要一个字节,所以可以考虑将length编入offset中,既offset低位保存length,再节省空间。

————————————————
版权声明:本文为CSDN博主「push_rbp」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_52329237/article/details/109555089

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
lzma算法是一种用于数据压缩的算法,它采用了Lempel-Ziv算法和移动平均算法。在C++中,可以使用LZMA SDK来实现该算法。 首先,需要从LZMA SDK官网下载LZMA SDK,并将其解压缩到本地目录中。然后,在代码中包含头文件"lzma.h",并使用以下代码来进行压缩: ```c++ #include <lzma.h> #include <cstdio> int main() { // 原始数据 char* src = "Hello, world! This is a test for LZMA compression."; // 原始数据长度 size_t src_len = strlen(src) + 1; // 压缩后的数据 char* dst = new char[src_len]; // 压缩后的数据长度 size_t dst_len = src_len; // 压缩级别(1-9,级别越高,压缩率越高,但耗时也越长) int level = 9; // 初始化压缩器 lzma_stream stream = { 0 }; lzma_ret ret = lzma_easy_encoder(&stream, level, LZMA_CHECK_CRC64); // 压缩数据 stream.next_in = (const uint8_t*)src; stream.avail_in = src_len; stream.next_out = (uint8_t*)dst; stream.avail_out = dst_len; ret = lzma_code(&stream, LZMA_FINISH); // 销毁压缩器 lzma_end(&stream); // 输出压缩后的数据 printf("Compressed data: %s\n", dst); // 释放内存 delete[] dst; return 0; } ``` 上述代码中,首先定义了原始数据和压缩后的数据的指针和长度,然后定义了压缩级别。接着,使用`lzma_easy_encoder`函数初始化一个压缩器,并指定压缩级别和校验类型。然后,将原始数据传入压缩器中,进行压缩。最后,使用`lzma_end`函数销毁压缩器,并输出压缩后的数据。 同样地,也可以使用以下代码来进行解压缩: ```c++ #include <lzma.h> #include <cstdio> int main() { // 压缩后的数据 char* src = "...\x5d\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00"; // 压缩后的数据 // 压缩后的数据长度 size_t src_len = 16; // 解压后的数据 char* dst = new char[1024]; // 解压后的数据长度 size_t dst_len = 1024; // 初始化解压器 lzma_stream stream = { 0 }; lzma_ret ret = lzma_stream_decoder(&stream, UINT64_MAX, 0); // 解压数据 stream.next_in = (const uint8_t*)src; stream.avail_in = src_len; stream.next_out = (uint8_t*)dst; stream.avail_out = dst_len; ret = lzma_code(&stream, LZMA_FINISH); // 销毁解压器 lzma_end(&stream); // 输出解压后的数据 printf("Decompressed data: %s\n", dst); // 释放内存 delete[] dst; return 0; } ``` 上述代码中,首先定义了压缩后的数据和解压后的数据的指针和长度。然后,使用`lzma_stream_decoder`函数初始化一个解压器,并指定解压器的内存限制和解压器的特性。接着,将压缩后的数据传入解压器中,进行解压缩。最后,使用`lzma_end`函数销毁解压器,并输出解压后的数据。 需要注意的是,由于LZMA算法的特性,压缩后的数据可能会比原始数据更长,因此在定义压缩后的数据的指针和长度时,需要预留足够的空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值