找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 14963|回复: 8

【C】7z源代码的使用——LZMA压缩解压算法

[复制链接]
发表于 2014-4-22 06:35:44 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
7z是目前流行的高压缩比压缩软件。它的压缩比甚至超过了WinRAR的最高压缩+固实压缩包(虽然从速度上WinRAR比7z快很多)。
7z使用了LZMA算法(这个LZMA不要把它联想成“楼主默哀”的拼音首字母)。而且7z是开源的。因此我们可以引用7z的压缩代码进行压缩解压。
首先我们需要下载7z的源代码。
LZMA的SDK官网:
http://www.7-zip.org/sdk.html
LZMA SDK下载地址:
http://ncu.dl.sourceforge.net/project/sevenzip/LZMA%20SDK/lzma920.tar.bz2

笔者下载的是9.20稳定版的源码。
从源代码中我们会看到很多文件。其中,LZMA的算法部分是以下这些文件:(在C文件夹内)
Alloc.c
Alloc.h
Bra.c
Bra.h
Bra86.c
BraIA64.c
LzFind.c
LzFind.h
LzFindMt.c
LzFindMt.h
LzHash.h
Lzma2Dec.c
Lzma2Dec.h
Lzma2Enc.c
Lzma2Enc.h
Lzma86.h
Lzma86Dec.c
Lzma86Enc.c
LzmaDec.c
LzmaDec.h
LzmaEnc.c
LzmaEnc.h
LzmaLib.c
LzmaLib.h
MtCoder.c
MtCoder.h
Threads.c
Threads.h
Types.h

好像很多的样子。把它们复制出来吧。
其中对于使用LZMA的我们来说,只需要关心LzmaLib.h即可。这个头文件也很简单,导出了两个函数,LzmaCompress和LzmaUncompress。
LzmaCompress用于压缩,而LzmaUncompress用于解压缩。
它们的原型如下:
  1. MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen,
  2.   unsigned char *outProps, size_t *outPropsSize, /* *outPropsSize must be = 5 */
  3.   int level,      /* 0 <= level <= 9, default = 5 */
  4.   unsigned dictSize,  /* default = (1 << 24) */
  5.   int lc,        /* 0 <= lc <= 8, default = 3  */
  6.   int lp,        /* 0 <= lp <= 4, default = 0  */
  7.   int pb,        /* 0 <= pb <= 4, default = 2  */
  8.   int fb,        /* 5 <= fb <= 273, default = 32 */
  9.   int numThreads /* 1 or 2, default = 2 */
  10.   );
  11. MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, SizeT *srcLen,
  12.   const unsigned char *props, size_t propsSize);
复制代码
其中有个概念是“Props”(全写应该是Properties,属性的意思)
LzmaCompress在压缩的时候会产生一个5字节的包,存储“属性”。在解压缩的时候,LzmaUncompress就需要你提供这个包,否则无法完成解压缩。
参数lc、lp、pb、fb、numThreads可以直接给-1,这样LzmaCompress就会自动填写默认值。dictSize给0也表示使用默认值。
因为属性包的大小目前被定死为5字节,因此*outPropsSize的值必须为5。outProps这个内存块也要有至少5字节的容量。
这里笔者我写了一些代码来应用LZMA的算法完成一些非常简单的压缩和解压缩的试验。
我建立了一个VC6的工程(VS2012在我这台笔记本上启动太慢,而且它会产生一些奇怪的东西总之就是麻烦。),新建了一个Entry.c作为程序入口点。

试验的方法是编写一个专门用来压缩、解压单个文件的程序,来试试效果。

代码如下。
  1. #include<stdio.h>
  2. #include<malloc.h>
  3. #include"lzmalib.h"

  4. void Usage()
  5. {
  6.     fputs(
  7.         "Usage:\n"
  8.         "LZMAComp <command> <input> <output>\n"
  9.         "Command:\n"
  10.         "  -c: Compress a single file <input> into <output>.\n"
  11.         "  -d: Decompress a single file <input> into <output>.\n",stderr);
  12. }

  13. int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize);
  14. int DecompressFile(FILE*fpOut,FILE*fpIn);

  15. int main(int argc,char**argv)
  16. {
  17.     if(argc<4)
  18.     {
  19.         Usage();
  20.         return 1;
  21.     }
  22.     if(!stricmp(argv[1],"-c"))//压缩一个文件
  23.     {
  24.         FILE*fp=fopen(argv[2],"rb");
  25.         FILE*fpout=fopen(argv[3],"wb");
  26.         int iRet;
  27.         unsigned long fLen;
  28.         if(!fp)
  29.         {
  30.             fprintf(stderr,"Unable to open %s\n",argv[2]);
  31.             return 2;
  32.         }
  33.         if(!fpout)
  34.         {
  35.             fprintf(stderr,"Unable to write %s\n",argv[3]);
  36.             return 2;
  37.         }
  38.         fseek(fp,0,SEEK_END);
  39.         fLen=ftell(fp);
  40.         fseek(fp,0,SEEK_SET);
  41.         printf("Input file size=%u\n",fLen);
  42.         iRet=CompressFile(fpout,fp,fLen);
  43.         if(iRet)
  44.             fprintf(stderr,"Error:%d\n",iRet);
  45.         fclose(fpout);
  46.         fclose(fp);
  47.         if(iRet)
  48.             unlink(argv[3]);
  49.         return iRet;
  50.     }
  51.     if(!stricmp(argv[1],"-d"))//解压一个文件
  52.     {
  53.         FILE*fp=fopen(argv[2],"rb");
  54.         FILE*fpout=fopen(argv[3],"wb");
  55.         int iRet;
  56.         if(!fp)
  57.         {
  58.             fprintf(stderr,"Unable to open %s\n",argv[2]);
  59.             return 2;
  60.         }
  61.         if(!fpout)
  62.         {
  63.             fprintf(stderr,"Unable to write %s\n",argv[3]);
  64.             return 2;
  65.         }
  66.         iRet=DecompressFile(fpout,fp);
  67.         if(iRet)
  68.             fprintf(stderr,"Error:%d\n",iRet);
  69.         fclose(fpout);
  70.         fclose(fp);
  71.         if(iRet)
  72.             unlink(argv[3]);
  73.         return iRet;
  74.     }
  75.     Usage();
  76.     return 1;
  77. }

  78. int CompressFile(FILE*fpOut,FILE*fpIn,unsigned long InSize)
  79. {
  80.     void*pInBuffer;//输入缓冲区
  81.     void*pOutBuffer;//输出缓冲区
  82.     unsigned long OutSize;//输出缓冲区大小

  83.     unsigned char Props[LZMA_PROPS_SIZE];//属性
  84.     size_t PropsSize=LZMA_PROPS_SIZE;//属性大小

  85.     pInBuffer=malloc(InSize);//缓冲区分配内存
  86.     pOutBuffer=malloc(OutSize=InSize);//输出缓冲区分配和输入缓冲区一样大的内存
  87.     if(!pInBuffer||!pOutBuffer)
  88.     {
  89.         free(pInBuffer);
  90.         free(pOutBuffer);
  91.         return 2;
  92.     }

  93.     fread(pInBuffer,1,InSize,fpIn);//读取文件

  94.     switch(LzmaCompress(//开始压缩
  95.         pOutBuffer,&OutSize,//输出缓冲区,大小
  96.         pInBuffer,InSize,//输入缓冲区,大小
  97.         Props,&PropsSize,//属性,属性大小
  98.         9,0,-1,-1,-1,-1,-1))//压缩比最大。其余全部取默认
  99.     {
  100.     case SZ_OK://成功完成
  101.         fwrite(&InSize,1,sizeof(InSize),fpOut);//写入原数据大小
  102.         fwrite(&OutSize,1,sizeof(OutSize),fpOut);//写入解压后的数据大小
  103.         fwrite(Props,1,PropsSize,fpOut);//写入属性
  104.         fwrite(pOutBuffer,1,OutSize,fpOut);//写入缓冲区
  105.         free(pInBuffer);//释放内存
  106.         free(pOutBuffer);
  107.         return 0;
  108.     case SZ_ERROR_PARAM://参数错误
  109.         free(pInBuffer);
  110.         free(pOutBuffer);
  111.         return 1;
  112.     default:
  113.     case SZ_ERROR_MEM://内存分配错误
  114.     case SZ_ERROR_THREAD://线程错误
  115.         free(pInBuffer);
  116.         free(pOutBuffer);
  117.         return 2;
  118.     case SZ_ERROR_OUTPUT_EOF://缓冲区过小
  119.         free(pInBuffer);
  120.         free(pOutBuffer);
  121.         return 3;
  122.     }
  123. }

  124. int DecompressFile(FILE*fpOut,FILE*fpIn)
  125. {
  126.     void*pSrcBuffer;
  127.     size_t InSize;

  128.     void*pDestBuffer;
  129.     size_t OutSize;

  130.     unsigned char Props[LZMA_PROPS_SIZE];

  131.     fread(&OutSize,1,sizeof(OutSize),fpIn);//读取原数据大小
  132.     fread(&InSize,1,sizeof(InSize),fpIn);//读取压缩后的数据大小

  133.     pDestBuffer=malloc(OutSize);//分配内存
  134.     pSrcBuffer=malloc(InSize);//分配内存
  135.     if(!pSrcBuffer||!pDestBuffer)//内存不足
  136.     {
  137.         free(pSrcBuffer);
  138.         free(pDestBuffer);
  139.         return 2;
  140.     }

  141.     fread(Props,1,sizeof(Props),fpIn);
  142.     fread(pSrcBuffer,1,InSize,fpIn);

  143.     switch(LzmaUncompress(pDestBuffer,&OutSize,pSrcBuffer,&InSize,Props,sizeof(Props)))
  144.     {
  145.     case SZ_OK:
  146.         fwrite(pDestBuffer,1,OutSize,fpOut);
  147.         free(pDestBuffer);
  148.         free(pSrcBuffer);
  149.         return 0;
  150.     case SZ_ERROR_DATA:
  151.     case SZ_ERROR_UNSUPPORTED:
  152.     case SZ_ERROR_INPUT_EOF:
  153.         free(pDestBuffer);
  154.         free(pSrcBuffer);
  155.         return 1;
  156.     default:
  157.     case SZ_ERROR_MEM:
  158.         free(pDestBuffer);
  159.         free(pSrcBuffer);
  160.         return 2;
  161.     }
  162. }
复制代码
然后就是测试的部分了。这里笔者就懒得专门跑去找文件进行测试了。我们就试试让它压缩Entry.c这个文件为Entry.lzma,然后再解压Entry.lzma到dec.txt。看看效果如何。
20140422062109.png
这应该是没有出错吧。哟西。那么我们来看看得到的文件。
20140422062302.png
嗯,我这边看了一下属性,Entry.lzma确实比Entry.c小很多。然后我们看看解压得到的dec.txt的内容。
20140422062424.png
经鉴定没有问题。接下来用WinHex看看Entry.lzma是个什么样子。
20140422062626.png
因为我的程序会在文件开头写入一些信息,因此第一个DWORD的值是解压后数据的大小,第二个DWORD的值是压缩后的数据的大小,然后5个字节是Props的值,后面的内容就是压缩包的内容了。总之LZMA的算法我懒得去研究了,所以这些压缩后的数据怎么解读这个我也不知道。感兴趣的可以去研究一下LzmaUncompress的实现。
如果你需要经常使用LZMA进行压缩解压,请抽个时间将其做成LIB静态库或者DLL动态库。这样就不用每次都复制粘贴这么多源码了。
LZMA的压缩和解压都比zlib慢,但是压缩率就比zlib高很多。大家可以根据情况来选择使用LZMA还是zlib。
个人感觉LZMA的接口挺方便的,比起zlib还要设置z_stream结构体来说,LZMA这种显得更直观。
EXE下载:
LZMAComp.exe (80 KB, 下载次数: 2, 售价: 1 个宅币)
源码下载:
LZMAComp.7z (75.13 KB, 下载次数: 17, 售价: 10 个宅币)

本帖被以下淘专辑推荐:

回复

使用道具 举报

 楼主| 发表于 2014-4-25 14:35:41 | 显示全部楼层
没人回帖?唉。。。
突然觉得自己可以发明一种新的图片格式:7png。我们平时见到的png图片都是用zlib压缩的。我弄一个LZMA压缩的png出来,估计一定比正常的png小很多吧?那么就把它起名为7png。一定很有意思。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-5-3 14:06:41 来自手机 | 显示全部楼层
这东西也可以拿来给网络传输的数据进行加密、压缩。
回复 赞! 靠!

使用道具 举报

发表于 2014-5-26 19:08:29 | 显示全部楼层
楼主,在不?
用你的方法,出现了一些bug诶,首先是有一些文件不能压缩,其次是,多个文件一起压缩时候,就不能解压了?
请问该怎么办呢?
回复 赞! 靠!

使用道具 举报

发表于 2014-5-26 19:17:59 | 显示全部楼层
请问楼主,这个缓冲区大小怎么定义呢?
压缩时最常见错误就是缓冲区过小呢
回复 赞! 靠!

使用道具 举报

发表于 2014-5-26 19:52:20 | 显示全部楼层
一个关键型问题,为什么我解压缩不成功呢,总是error:1
用的是vs2010 vc6.0 也试过了,一开始解压图片不成功,后来按照楼主的帖子上的Entry。c来压缩,成功,但是解压缩不成功呢,,,,
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-6-11 15:07:26 | 显示全部楼层
琦天大圣orz 发表于 2014-5-26 11:08
楼主,在不?
用你的方法,出现了一些bug诶,首先是有一些文件不能压缩,其次是,多个文件一起压缩时候,就 ...

我的这个实例只是用于压缩、解压单个文件,作为7z的使用范例。肯定不能压缩多个文件。想得美。
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-6-11 15:07:59 | 显示全部楼层
琦天大圣orz 发表于 2014-5-26 11:17
请问楼主,这个缓冲区大小怎么定义呢?
压缩时最常见错误就是缓冲区过小呢 ...

缓冲区过小是因为,那个文件已经没有压缩的必要了(因为它被压缩后体积反而膨胀了)
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2014-6-11 15:08:40 | 显示全部楼层
琦天大圣orz 发表于 2014-5-26 11:52
一个关键型问题,为什么我解压缩不成功呢,总是error:1
用的是vs2010 vc6.0 也试过了,一开始解压图片不成 ...

如果你直接下载我的LZMAComp的源码就会成功。我是试过的,都没有问题。
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-23 21:31 , Processed in 0.042921 second(s), 31 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表