热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

无损压缩算法专题——LZSS算法实现

本文介绍了基于无损压缩算法专题的LZSS算法实现。通过Python和C两种语言的代码实现了对任意文件的压缩和解压功能。详细介绍了LZSS算法的原理和实现过程,以及代码中的注释。

一、前言

本文是基于我的上一篇博客《无损压缩算法专题——无损压缩算法介绍》的基础上来实现的,博客链接https://blog.csdn.net/qq_34254642/article/details/103651815,这一篇当中实现基本的LZSS算法功能,先不做改进,所以算法效率比较低,但是便于理解。写了Python和C两个版本,两种语言的代码结构是一样的,代码中都有详尽的注释。实现了对任意文件的压缩和解压功能。

 


二、LZSS算法实现

Python实现

import ctypes
import osclass LZSS():def __init__(self, preBufSizeBits):self.threshold = 2 #长度大于等于2的匹配串才有必要压缩self.preBufSizeBits = preBufSizeBits #前向缓冲区占用的比特位self.windowBufSizeBits = 16 - self.preBufSizeBits #滑动窗口占用的比特位self.preBufSize = (1 <= 8:writebytes = bytes(ctypes.c_uint8(signbits)) + restorebufffwrite.write(writebytes);itemnum = 0signbits = 0restorebuff = b&#39;&#39;self.preBuf = self.preBuf[len(self.matchString):] #将刚刚匹配过的数据移出前向缓冲区self.windowBuf += self.matchString #将刚刚匹配过的数据加入滑动窗口if len(self.windowBuf) > self.windowBufSize: #将多出的数据从前面开始移出滑动窗口self.windowBuf = self.windowBuf[(len(self.windowBuf) - self.windowBufSize):]self.preBuf += fread.read(self.preBufSize - len(self.preBuf)) #读取数据补充前向缓冲区if restorebuff != b&#39;&#39;: #文件最后可能不满一组数据量,直接写到文件里writebytes = bytes(ctypes.c_uint8(signbits)) + restorebufffwrite.write(writebytes);fread.close()fwrite.close()return os.path.getsize(writefilename)#文件解压def LZSS_decode(self, readfilename, writefilename):fread = open(readfilename, "rb")fwrite = open(writefilename, "wb")self.windowBuf = b&#39;&#39;self.preBuf = fread.read(1) #先读一个标记字节以确定接下来怎么解压数据while self.preBuf != b&#39;&#39;:for i in range(8): #8个项目为一组进行解压# 从标记字节的最高位开始解析,0代表原始数据,1代表(下标,匹配数)解析if self.preBuf[0] & (1 <<(7 - i)) == 0:temp = fread.read(1)fwrite.write(temp)self.windowBuf += tempelse:temp = fread.read(2)start = ((temp[0] + temp[1] * 256) // (1 < self.windowBufSize: #限制滑动窗口大小self.windowBuf = self.windowBuf[(len(self.windowBuf) - self.windowBufSize):]self.preBuf = fread.read(1) #读取下一组数据的标志字节fread.close()fwrite.close()if __name__ == &#39;__main__&#39;:Demo = LZSS(7)Demo.LZSS_encode("115.log", "encode")Demo.LZSS_decode("encode", "decode")

C实现

#include
#include #define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int#define TRUE 1
#define FALSE 0BYTE bThreshold; //压缩阈值、长度大于等于2的匹配串才有必要压缩BYTE bPreBufSizeBits; //前向缓冲区占用的比特位
BYTE bWindowBufSizeBits; //滑动窗口占用的比特位WORD wPreBufSize; //通过占用的比特位计算缓冲区大小
WORD wWindowBufSize; //通过占用的比特位计算滑动窗口大小BYTE bPreBuf[1024]; //前向缓冲区
BYTE bWindowBuf[8192]; //滑动窗口
BYTE bMatchString[1024]; //匹配串
WORD wMatchIndex; //滑动窗口匹配串起始下标BYTE FindSameString(BYTE *pbStrA, WORD wLenA, BYTE *pbStrB, WORD wLenB, WORD *pwMatchIndex); //查找匹配串
DWORD LZSS_encode(char *pbReadFileName, char *pbWriteFileName); //文件压缩
DWORD LZSS_decode(char *pbReadFileName, char *pbWriteFileName); //文件解压int main()
{bThreshold = 2;bPreBufSizeBits = 6;bWindowBufSizeBits = 16 - bPreBufSizeBits;wPreBufSize = ((WORD)1 <}BYTE FindSameString(BYTE *pbStrA, WORD wLenA, BYTE *pbStrB, WORD wLenB, WORD *pwMatchIndex)
{WORD i, j;for (i = 0; i }DWORD LZSS_encode(char *pbReadFileName, char *pbWriteFileName)
{WORD i, j;WORD wPreBufCnt = 0;WORD wWindowBufCnt = 0;WORD wMatchStringCnt = 0;BYTE bRestoreBuf[17] = { 0 };BYTE bRestoreBufCnt = 1;BYTE bItemNum = 0;FILE *pfRead = fopen(pbReadFileName, "rb");FILE *pfWrite = fopen(pbWriteFileName, "wb");//前向缓冲区没数据可操作了即为压缩结束while (wPreBufCnt += fread(&bPreBuf[wPreBufCnt], 1, wPreBufSize - wPreBufCnt, pfRead)){wMatchStringCnt = 0; //刚开始没有匹配到数据wMatchIndex = 0xFFFF; //初始化一个最大值,表示没匹配到for (i = bThreshold; i <= wPreBufCnt; i++) //在滑动窗口中寻找最长的匹配串{if (TRUE == FindSameString(bWindowBuf, wWindowBufCnt, bPreBuf, i, &wMatchIndex)){memcpy(bMatchString, &bWindowBuf[wMatchIndex], i);wMatchStringCnt = i;}else{break;}}//如果没找到匹配串或者匹配长度为1,直接输出原始数据if ((0xFFFF == wMatchIndex)){wMatchStringCnt = 1;bMatchString[0] = bPreBuf[0];bRestoreBuf[bRestoreBufCnt++] = bPreBuf[0];}else{j = (wMatchIndex <> 8);bRestoreBuf[0] |= (BYTE)1 <<(7 - bItemNum);}bItemNum += 1; //操作完一个项目+1if (bItemNum >= 8) //项目数达到8了,说明做完了一组压缩,将这一组数据写入文件,同时清空缓存{fwrite(bRestoreBuf, 1, bRestoreBufCnt, pfWrite);bItemNum = 0;memset(bRestoreBuf, 0, sizeof(bRestoreBuf));bRestoreBufCnt = 1;}//将刚刚匹配过的数据移出前向缓冲区for (i = 0; i <(wPreBufCnt - wMatchStringCnt); i++){bPreBuf[i] = bPreBuf[i + wMatchStringCnt];}wPreBufCnt -= wMatchStringCnt;//如果滑动窗口将要溢出,先提前把前面的部分数据移出窗口if ((wWindowBufCnt + wMatchStringCnt) > wWindowBufSize){j = ((wWindowBufCnt + wMatchStringCnt) - wWindowBufSize);for (i = 0; i <(wWindowBufSize - j); i++){bWindowBuf[i] = bWindowBuf[i + j];}wWindowBufCnt = wWindowBufSize - wMatchStringCnt;}//将刚刚匹配过的数据加入滑动窗口memcpy((BYTE *)&bWindowBuf[wWindowBufCnt], bMatchString, wMatchStringCnt);wWindowBufCnt += wMatchStringCnt;}//文件最后可能不满一组数据量,直接写到文件里if (0 != bRestoreBufCnt){fwrite(bRestoreBuf, 1, bRestoreBufCnt, pfWrite);}fclose(pfRead);fclose(pfWrite);return 0;
}DWORD LZSS_decode(char *pbReadFileName, char *pbWriteFileName)
{WORD i, j;BYTE bItemNum;BYTE bFlag;WORD wStart;WORD wMatchStringCnt = 0;WORD wWindowBufCnt = 0;FILE *pfRead = fopen(pbReadFileName, "rb");FILE *pfWrite = fopen(pbWriteFileName, "wb");while (0 != fread(&bFlag, 1, 1, pfRead)) //先读一个标记字节以确定接下来怎么解压数据{for (bItemNum = 0; bItemNum <8; bItemNum++) //8个项目为一组进行解压{//从标记字节的最高位开始解析,0代表原始数据,1代表(下标,匹配数)解析if (0 == (bFlag & ((BYTE)1 <<(7 - bItemNum)))){if (fread(bPreBuf, 1, 1, pfRead) <1){goto LZSS_decode_out_;}fwrite(bPreBuf, 1, 1, pfWrite);bMatchString[0] = bPreBuf[0];wMatchStringCnt = 1;}else{if (fread(bPreBuf, 1, 2, pfRead) <2){goto LZSS_decode_out_;}//取出高位的滑动窗口匹配串下标wStart = ((WORD)bPreBuf[0] | ((WORD)bPreBuf[1] <<8)) / ((WORD)1 < wWindowBufSize){j = (wWindowBufCnt + wMatchStringCnt) - wWindowBufSize;for (i = 0; i }

三、性能分析

因为代码都是最基本的实现,并没有对匹配串搜索函数进行优化,所以时间性能上比较低,我们这里只对比压缩前后文件的字节大小。有一点要提到的就是代码里面有个threshold参数为2,意思是匹配串大于等于2才有必要进行压缩,因为(位置,长度)这个标记的输出长度为16比特位,所以匹配串只有1字节也用这种方式表示的话显然起不到压缩效果。大家还可以发现一个问题,写入文件时匹配长度并不是实际值,因为0和1的匹配长度是不存在的,所以干脆把0当做2,1当做3这样来看待,就可以把匹配长度扩充两个长度了。

确定了(位置,长度)的输出格式为两个字节后,接下里就是怎么选取“位置”和“长度”各自所应该占用的比特位是多少了,我们可以设置为(8,8),(9,7),(10,6)等等这样的组合,但是给“位置”设置的比特位一定要大于等于“长度”的比特位,原因是滑动窗口大小要大于等于前向缓冲区大小,不然没有意义。对于相同的文件,这两个参数选取不同,那么压缩率也会不同,下面是我对一个log文件选取不同的参数进行压缩后的文件大小对比图,图中preBufSizeBits就是指的“长度”所占的比特位数:

 未压缩的原始文件大小是5.06MB,可以清晰的看出preBufSizeBits并不是越小越好,也不是越大越好,因为如果preBufSizeBits小了的话那么前向缓冲区也就小了,一次能匹配的数据串就小了;如果preBufSizeBits大了的话那么虽然前向缓冲区变大了,但是滑动窗口会缩小,数据串的匹配范围就变小了。所以需要选择一个合适的值才能使文件压缩性能最好。

算法内部的对比就到这里了,接下来我们和当前流行的ZIP和RAR进行压缩性能对比,虽然感觉自不量力,但是有对比才有进步嘛。

115.log是原始文件,encode2到encode8其实就是上边性能图里不同preBufSizeBits时生成的压缩文件,decode2到decode8是对应再解压缩出来的文件。115.rar是用电脑上RAR软件压缩的文件,115.zip是用电脑上ZIP软件压缩的文件。我们这个算法最好的性能是压缩到了197KB,和ZIP的161KB差距不是特别大,和RAR就差了相当一大截了。 因为ZIP其实也是类似的滑动窗口匹配压缩,所以接下来优化LZSS算法的话,还是要尽量向ZIP的性能看齐。


四、总结

接下来还会继续思考LZSS算法的改进,包括压缩率和压缩/解压时间的性能,因为我本人是嵌入式工程师,所以会思考实现在嵌入式设备上如何运用数据压缩,在网上查找资料的时候也找到了一些开源的压缩库,但是使用上可能会存在一些问题和限制,当然最好是我们使用的算法我们是知根知底的,这样我们就可以根据项目的需求和特点进行灵活的修改和运用,出了什么问题也不会太慌。


推荐阅读
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文介绍了Python对Excel文件的读取方法,包括模块的安装和使用。通过安装xlrd、xlwt、xlutils、pyExcelerator等模块,可以实现对Excel文件的读取和处理。具体的读取方法包括打开excel文件、抓取所有sheet的名称、定位到指定的表单等。本文提供了两种定位表单的方式,并给出了相应的代码示例。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 基于dlib的人脸68特征点提取(眨眼张嘴检测)python版本
    文章目录引言开发环境和库流程设计张嘴和闭眼的检测引言(1)利用Dlib官方训练好的模型“shape_predictor_68_face_landmarks.dat”进行68个点标定 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
author-avatar
有你真好cc_693
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有