news 2026/10/19 1:24:25

7-Zip源码深度解析:从LZMA压缩算法到二次开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
7-Zip源码深度解析:从LZMA压缩算法到二次开发实践

简介:7ZIP源码是一份面向软件开发者与算法学习者的开源压缩软件完整实现包,涵盖压缩解压缩核心功能、多语言版本、DLL生成与命令行工具设计。这份资源定位在逆向研究、跨平台开发及算法优化场景,尤其适合希望深入理解LZMA无损压缩原理的程序员。包体共643个文件,压缩后1.72MB,以C++头文件与实现(h/cpp)、C源码、C#与Java扩展、makefile构建脚本、rc界面资源、asm汇编优化等类型为主,结构清晰,便于按模块查阅。已有660人学习,适合在读完压缩算法理论后对照源码印证细节。价值点在于:可研究C++、C#、Java不同语言对同一功能的实现差异,学习如何将压缩能力封装为DLL;命令行工具部分能展示参数解析与复杂文件操作逻辑;汇编代码与LZMA编码器源码则有助于理解高性能压缩优化策略,无论用于个人进阶还是项目集成,都有较强的参考意义。

1. 7-Zip源码能吃透什么:一本用C++写出来的压缩算法活教材

大多数人第一次接触7-Zip,只是把它当成一个压缩率更高的绿色软件。真正让它在技术圈里地位特殊的,是这份用C++写出的完整源码:从7z容器格式、LZMA/LZMA2压缩算法,到固实打包、分卷、自解压、加密,整套现代压缩工具箱的实现都在里面。如果你正在做文件格式解析、嵌入式存储、服务端日志压缩,或者只是想把压缩这个黑匣子打开看一眼,这份源码比任何压缩算法书都直接。它能回答三个具体问题:LZMA的字典匹配是怎么实现的?7z格式的头部到底写了什么?怎么把一个压缩库嵌进自己的工程?本文我会按工程结构、核心代码、编译运行、踩坑记录和改造思路的顺序把它拆开讲。

2. 工程结构拆解:模块边界与COM风格接口

2.1 源码根目录的分层逻辑

打开7-Zip源码包,第一眼会看到一个很经典的C++工程布局。根目录下CPP/7zip是全部核心代码所在,里面按职责分成了几个互不越界的目录:

  • 7zip/Common:底层公共代码,ByteBuffer、Defs.h、MyString、CRC计算这类基础件,基本不依赖平台,是所有模块的地基。
  • 7zip/Compress:压缩算法层,LZMA、LZMA2、BZip2、PPMd、Deflate等编解码器都在这里,每个算法一个子目录。
  • 7zip/Crypto:加密层,7z格式里的AES-256加密、ZipCrypto、哈希计算(CRC32、SHA、BLAKE2等)在这里实现。
  • 7zip/Archive:容器格式层,NArchive::N7z负责读写7z格式,NZip、NTar、NRar(只读)等负责解析其它格式。这一层把"一堆文件"组装成"一个压缩包",或者反过来解开。
  • 7zip/UI:用户界面层,命令行工具、文件管理器右键扩展、SFX自解压模块的引导逻辑都在这里,也是离软件最近的一层。

这个分层的顺序不是随便排的。Common不依赖Compress,Compress不依赖Archive,Archive不依赖UI,UI只在最外层调用下层接口。换句话说,如果你只想要一个能解压7z的SDK,直接把UI目录扔掉,从Archive和Compress里挑文件就行。这种倒金字塔依赖关系,是这份源码能裁剪的最小前提。

目录职责典型子模块
Common基础工具与平台抽象Defs.h、MyVector、CRC
Compress压缩算法编解码器Lzma、Lzma2、Ppmd、BZip2
Crypto加密与哈希Aes、MyCrypt、Sha
Archive容器读写与格式转换NArchive::N7z、NZip、NTar
UI命令行与图形交互7z.exe、7zFM、SFX

从依赖顺序看,这是一个典型的"算法可替换、容器可扩展"架构。加一种新压缩算法,只需要在Compress目录里实现一组统一的编解码器接口,再到注册表登记;加一种新容器格式,也只需要在Archive目录里实现IInArchive/IOutArchive两个大接口。7-Zip之所以能处理这么多格式,靠的不是每种格式写一套独立逻辑,而是这套统一的接口约定。

2.2 整个SDK的基石:IInArchive / IOutArchive

在7-Zip的代码里,你看不到一堆继承层次很深的抽象基类,取而代之的是一组COM风格的接口。所谓COM风格,是指这些接口本质上是一个虚函数表,每个接口继承自IUnknown,通过QueryInterface做类型转换。这样写的好处是:编译器不会把接口类型和具体实现强绑定,不同模块之间只需要依赖头文件里声明的接口签名,而不依赖具体类的头文件。

其中最重要的两个接口是IInArchive(读压缩包)和IOutArchive(写压缩包)。拿IInArchive来说,它定义了一组非常直白的方法:

  • Open:打开一个压缩包流,读取格式信息。
  • GetNumberOfItems:获得压缩包里的文件条目数量。
  • GetProperty:按属性ID取文件元数据,比如文件名、大小、CRC32、修改时间。
  • Extract:提取指定索引范围的文件,把解压后的字节交给回调流。
  • Close:释放资源。

源码里的关键实现大概是这个形态:

class CArchiveHandler : public IInArchive { public: MY_UNKNOWN_IMP1(IInArchive) // 定义 QueryInterface/AddRef/Release STDMETHOD(Open)(ISequentialInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback); STDMETHOD(GetNumberOfItems)(UInt32 *numItems); STDMETHOD(Extract)(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback); };

这里的Extract有一个关键参数testMode:传入非0时只读流不写数据,用来做解压自检,不会落盘。很多二次开发者在解压前会先跑一遍testMode,确认压缩包没坏再真正写文件,这就是它存在的意义。

Open方法接收一个ISequentialInStream作为输入流。这个流接口是所有读取操作的统一入口,它只有两个方法:Read和GetSize。无论你传入的是文件流、内存流还是网络流,只要实现了这个接口,7-Zip就能解压。反过来,写压缩包时用的是ISequentialOutStream,同样抽象。后面二次开发章节里,我会演示怎么用这个接口做内存到内存的压缩,不加一个临时文件。

2.3 Codec注册机制:加一个新算法要改哪几个文件

7-Zip的压缩算法在代码里被称为Codec,Compress目录下每个算法文件夹都有一个Register.cpp,里面通过宏把一个编解码器工厂对象挂到全局注册表上。注册表本质上是一个静态数组,存着一组CreateArc、CreateCodec这类函数指针。

常见的注册宏:

REGISTER_CODEC_CREATE_2(CreateLzmaEncoder, CLzmaEncoder) REGISTER_CODEC_CREATE_2(CreateLzmaDecoder, CLzmaDecoder) REGISTER_CODEC_7Z(CLzmaEncoder, CLzmaDecoder, 0x030101 /* LZMA 的 Codec ID */, "LZMA", "LZMA")

宏展开后做的事情,是在一个静态CodecInfo结构里登记编码器/解码器的创建函数、7z格式内的算法ID、算法名字和对齐方式。这样Archive层打开压缩包时,读到该算法的ID,就能从注册表里找到对应的创建函数,动态造出解码器。换句话说,你加的算法只要实现了ICompressCoder接口,然后在注册表里填上一行,7z容器就能认它。

LZMA的Codec ID是0x030101,这个数字直接写进7z文件头,解压端靠读这个字节序列来路由算法。所以二次开发时,如果你发明了一个私有压缩算法,需要给自己申请一个不会冲突的ID段——最省事的做法是放在0x03F70000之后的私有区间。

3. 核心链路代码阅读:一次压缩请求从界面到算法怎么走

3.1 起点:命令行参数如何进入Archive模块

当一个用户敲下下面的命令:

7z a archive.7z /data/logs

UI层(7z.exe的入口)先解析参数:a表示添加文件到压缩包,archive.7z是目标压缩包,/data/logs是待压缩的目录。参数解析完,UI会创建一个CArchiveHandler,然后调用它的UpdateItems方法。这是压缩操作的真正起点,它接收一组文件源、一个IOutArchive指针,以及一个回调接口。回调接口IArchiveUpdateCallback用于向界面回传进度百分比和文件名。

如果待压缩路径里有的文件在压缩包之外,UpdateItems会先启动一个"收集文件"阶段:遍历目录、计算每个文件的大小、读文件的属性。这一步完成后,才会进入实际的编码流程。很多人第一次读源码时在这里会被绕晕,因为UpdateItems之后不是直接进LZMA,而是先走一个"把文件分成固实块"的过程——它决定哪些文件放进同一个LZMA流。

这一步为什么重要?zip是每个文件独立压缩,最后拼成一个目录表;7z则是把一批文件的首尾字节连成一个"固实流",整体交给LZMA/LZMA2压缩。固实压缩的好处是压缩率更高(重复内容跨文件也能匹配到),代价是修改任意一个文件时,后面的固实流可能全部要重压。源码里这个决策在NArchive::N7z::CArchiveHandler的UpdateItems中完成,它会按压缩级别、文件数量和内存限制决定固实块大小。

3.2 容器层:7z格式的固实块与文件目录结构

7z文件的物理布局是一个三段式:Start Header(起始头)、固实数据流、End Header(结束头)。End Header里记录着一个文件目录,包含每个文件的名称、大小、CRC32、属性和它所属的固实块编号。"解压时先读尾部目录"是7z格式的一个重要特性,这让随机访问单个文件成为可能:先定位目录,找到文件在固实流中的偏移,再只解压这一段。源码里对应的是NArchive::N7z::CInArchive::ReadDatabase方法和CDatabaseEx结构。

CFileItem结构里最重要的几个字段是:

struct CFileItem { UInt64 Size; // 原文件大小 UInt32 Crc; // 原文件 CRC32 UInt32 Attrib; // 文件属性位掩码 bool IsDir; bool CrcDefined; UInt16 MTime; // 修改时间(低精度) };

注意这里没有文件名,文件的路径和编码信息保存在CDatabase的另一个数组里。这样设计是为了在解析目录时最小化内存占用——一个包含十万个文件的压缩包,光文件大小和CRC就要占不少空间,路径单独存放可以让目录解析更快。CrcDefined是解压后校验的开关:只有当这个字段为true时,解压器才会把算出的CRC与记录值比对,否则跳过校验。某些流式生成的7z包(比如一边录一边压)会因为写入时不知道最终CRC而把CrcDefined置为false。

3.3 算法层:LZMA的核心状态机与Range Coder

真正压缩率高的核心是LZMA算法,它本质上是"字典匹配 + 区间编码(Range Coder)"的组合。先说核心逻辑:LZMA维护一个滑动字典窗口,编码时从字典里寻找当前待编码字节的最长匹配串,匹配成功就输出一个"(距离, 长度)"对,匹配不上就输出一个原始字节;之后再把这些输出符号交给Range Coder做熵编码。源码里具体的入口是CLzmaEnc,核心循环在LzmaEnc.c——这个文件很值得从头读一遍,代码组织得相当清晰。

它的核心循环逻辑,简化后是这个样子:

// LZMA 编码主循环(简化示意,实际还要处理状态机迁移) while (nowPos < nowPos64) // 当前输入流还没到末尾 { // 1. 在滑动字典里查找最长匹配,得到 matchLen 和 matchDistance if (matchFinder->GetMatches(matches) > 0) { // 2a. 找到了匹配:编码"匹配"标记,再区间编码距离和长度 LitEnc_EncodeMatched(&p->rc, p->litProbs, p->state, rep0, len); LenEnc_Encode(&p->rc, &p->lenEnc, len, posState, p->state, 0); } else { // 2b. 没找到匹配:编码一个普通字节 LitEnc_Encode(&p->rc, p->litProbs, p->state, p->previousByte, byte); } // 3. 移动匹配查找器的窗口指针,把当前字节滑入字典 MatchFinder_Skip(matchFinder, len); nowPos += len; }

这里的matchFinder是"匹配查找器",它负责在字典窗口内找最长匹配。在LZMA源码里有多种实现:HashChain(哈希链)和BinaryTree(二叉树),通过btMode参数切换。二叉树模式匹配精度更高,压缩率略好,但更慢;哈希链模式速度快,内存占用低。这个循环每跑一次,就把一个LZMA符号——或者是一个"匹配距离+长度"对,或者是一个独立字节——送进Range Coder。

Range Coder是LZMA的熵编码器,实现位于RangeEnc.c。它用一段64位区间不断收窄来表示输出符号序列,每处理一个符号就调一次ShiftLow把高位移到输出字节流。它比传统的算术编码实现更简单,所以作者选它作为默认熵编码器。理解这对组合,你再看命令行里的压缩级别参数就顺了:级别越高,匹配查找器的搜索深度越大,字典越大,Range Coder的概率表更新也越充分,代价是时间和内存。

需要特别注意的是,CLzmaEnc是经过大量优化和内联的版本,函数里频繁出现宏、指针合并和位运算,初学者直接啃细节容易陷入微观拜读。我个人的读法是完全跳过matchFinder的内部实现,先只在窗口层理解它的行为:它对外只回答一个问题"当前位置往后看,字典里能匹配的最长字符串有多长、在哪",内部实现细节放到第二遍再啃。

3.4 多线程的边界:哪些部分真的会并行

7z源码里其实只有一个相对简单的线程池(CThreads和CSyncEvents),并行度主要体现在两个地方:一是固实块之间可以并行压缩(每个固实块一个LZMA编码器实例),二是一个大文件的LZMA2编码可以按块并行。注意LZMA1是严格串行的,LZMA2支持分块,块之间有独立的编码状态,块边界不共享字典,所以才能并行。源码中控制并行的核心参数是MT相关开关,默认在GUI里会按处理器核心数自动选择。命令行里可以通过-mmt=num指定线程数,比如-mmt=1强制单线程。多卷分卷、合并固实流的逻辑都发生在这一层之上,算法的并行几乎完全由LZMA2的分块特性支撑。

4. 编译与运行:在Windows和Linux上把源码跑起来

4.1 源码包里找构建入口

拿到源码包后先别急着开IDE。先看清构建入口在哪。7-Zip的源码目录结构是跨平台友好的:代码本体在CPP/7zip,平台相关的构建文件分散在各层。

  • Windows构建:在CPP/7zip/Bundles目录下有现成的工程文件,新版源码用的是Visual Studio解决方案(.sln),另外还有老的.dsw项目文件。打开后直接生成解决方案,输出会落在bin目录,里面有7z.exe(命令行版)和7z.dll(核心逻辑动态库)。
  • Linux/macOS构建:在CPP/7zip根目录下有makefile,其中makefile.gcc是通用的GCC构建入口。不需要autotools,直接make -f makefile.gcc就行。

注意,Windows上编译有两条路径:一种是把整个解决方案全编(生成7z.exe + 7z.dll),另一种是编译"全部静态链接"的7zr.exe,它把全部模块揉进一个可执行文件,适合做单文件工具。给出实际命令:

# Linux 构建 CLI 工具 + 7z 格式支持 cd CPP/7zip make -f makefile.gcc # 构建后二进制位置 ls -l bin/7z

如果只想要一个最小化的纯7z读包工具,可以只编译Bundles/Alone入口下的7zr。Alone是一个"不带加密、不带其它格式"的迷你构建,对应的工程或make目标很小。这个"最小化"目标对我来说很有用:它把代码量收敛到一个可以单步跟读的范围内。

编译参数注意:不要在make命令里混用-j和7-Zip自带的并行逻辑;如果你的gcc版本过新,偶尔会碰到LZMA代码里某些老的C风格写法触发编译警告,但不会报错。真正会让构建失败的是32/64位宏不一致,比如平台头文件里没有检查_WIN64,导致中间变量统一走32位路径,解压大文件时溢出。

4.2 验证构建结果:用命令行压一个测试集

编译成功只是第一步,关键是验证这个二进制是不是真的能工作。我每次编译完都会做三件事:

# 1. 基础压缩:目录压成 7z ./7z a test.7z ./mydir # 2. 列出压缩包内容,验证头部目录解析正常 ./7z l test.7z # 3. 解压到新目录,验证 CRC 校验通过 ./7z x test.7z -o./out -y

命令行参数里最有价值的是-m参数,它控制压缩方法和参数。例如:

  • -mmt=1:强制单线程。
  • -mx=9:最高压缩级别,效果是更大的字典和更慢的匹配搜索。
  • -ms=e:固实模式开关,e表示按扩展名分组固实。
  • -m0=LZMA2:d64m:lc3:lp0:pb2:用LZMA2,字典64MB,字面量上下文位数3,位置位数0,位置上下文位数2。

这里面lc/lp/pb三个参数是LZMA状态机里的关键维度。lc是字面量编码时取前几个字节作为上下文,lp是位置上下文,pb是区间编码时按什么粒度划分posState。改这些参数会直接影响压缩率和内存占用——这也是读源码时最有意思的实践,因为你能在-m0=参数后面直接验证源码里学到的概念。

4.3 静态链接与裁剪的取舍

常见的二次开发场景是:只想要一个静态链接的命令行工具,不依赖目标机器上的任何动态库,直接拷过去运行。做法是:

make -f makefile.gcc static

makefile里static目标会带上-static链接参数。但要注意,静态链接的7zr会显著变大,因为它把压缩算法、容器解析全编译进去了。正常发行版依赖动态库,体积小很多。如果是嵌入到嵌入式设备,我一般只保留LZMA编解码器这一块,通过DOC/lzma.txt里说明的C接口直接调编解码,完全不引入7z容器层。裁剪之后整个算法库约200~300KB级别,具体体积跟编译选项相关。

5. 常见问题与避坑:源码阅读与二次开发的五个典型坑

5.1 Debug断点打不进去,进函数全是汇编

现象:在CLzmaEnc里设断点,按F11后发现直接进入了一片内联汇编或被优化掉的空跳转,根本看不到匹配循环的流程。

原因:默认构建配置是Release,编译器把大量小函数内联掉了。LZMA编码器为了性能还专门用宏控制内联,_7ZIP_ST宏在单线程构建里可能干掉了部分状态检查,参数变量在寄存器间跳转。

解决:把构建改成Debug配置,并定义_7ZIP_DEBUG或直接去掉优化选项。我在Windows上一般用Debug配置编译完整解决方案,在Linux上用-O0 -g编译LzmaEnc.c单独生成一个调试目标。从那以后我读LZMA的匹配循环时就再没陷入汇编黑洞。

5.2 自实现的输出流在解压中途遭遇CRC错误

现象:用自定义ISequentialOutStream解压,文件写到一半时返回失败,报CRC错误。

原因:没理解Write方法的返回值约定。7-Zip的输出流Write的返回不是告诉我们"你写了多少",而是告诉调用者"你要不要停"。如果返回值与实际写入的字节数不一致,解压器会认为流被截断,置为失败。

解决:严格实现Write的语义:若传入的size参数大于0,则要么全写并返回size,要么以错误码中止。任何"部分写入"都会被当作错误处理。我后来在封装网络流时,采用"要么全收,要么报错"的策略,彻底避开这个坑。

5.3 打开自解压(SFX)压缩包总是找不到正确偏移

现象:把7z包和SFX引导程序合成为exe后,用IInArchive直接打开这个exe,文件头校验失败。

原因:7z数据被追加在引导程序尾部,头部偏移记录在文件尾部的StartHeader中;用Open方法直接打开时,7-Zip要求传入的流起始于7z数据开头。

解决:先读取尾部32字节找到StartHeader,计算7z数据的实际偏移量,然后用支持Seek的流(比如IInStream)把位置跳到偏移处再传入Open。常见的实现方式是继承CInArchive并传入一个"跳过前N字节"的流包装器。这个坑在源码里其实有注释说明,但如果你先读API而不是先读文档,很容易踩中。

5.4 Linux编译报错:找不到Windows.h或_msize未定义

现象:make -f makefile.gcc编译时大量编译单元报告Windows.h缺失,甚至在Common里报_msize未定义。

原因:makefile.gcc对应的是跨平台目标,但源文件里很多平台适配是靠宏开关切分的,比如_WIN32、_7ZIP_PLATFORM。如果编译器没有定义正确的宏(或者混用了不同的makefile),条件编译没有走到Unix分支,就把Windows分支的代码引出来了。

解决:检查makefile.gcc的预处理器定义;不要手动修改源码里的头文件去强匹配,而是统一编译参数。最干净的做法是只保留makefile.gcc一个入口,别用makefile(那是给Windows编译器准备的)。

5.5 内存占用是预期的三倍

现象:压缩一个大文件时,进程占用内存远大于预期字典大小。

原因:LZMA匹配查找器在内存里的不只是一个哈希表,还要维护窗口缓冲区、literal概率表、区间编码器状态等。特别是最高压缩级别默认开启bt4(二叉树匹配),每个窗口位置存储的左右子节点和附加信息比哈希链大一倍还多。

解决:控制-mx等级并显式设定字典大小。一般场景我用-mmt=1和-md=48m,既要快又不至于内存爆炸。内存敏感的场景我会在源码里把MatchFinder从bt4换成hc4,代价是压缩率略降,但内存占用减半。

6. 二次开发技巧:基于SDK把它改造成自己的压缩工具

6.1 裁剪出"只解压7z"的迷你SDK

如果你要做一个私有服务,每天接收一批7z压缩包并解压入库,不需要压缩功能,不需要多格式支持。我从这份源码里裁剪出来的最小集合是:7zip/Common里的基础文件 + Archive/7z的解压器 + Compress/Lzma的解码器 + Crypto(如果加密可选)。UI层全部不要,Archive目录里其它格式全部不要。这样编译产物会缩小到原来的五分之一左右。

最小解压调用链长这样(伪代码,引用源码里的函数):

// 1. 打开压缩包流 CMyComPtr<IInArchive> archive; CreateObject(&archive, CLSID_CFormat7z); // 注册表里找 7z 格式处理器 archive->Open(stream, 0, &openCallback); // 2. 遍历文件并解压 UInt32 numItems; archive->GetNumberOfItems(&numItems); for (UInt32 i = 0; i < numItems; i++) { CMyComPtr<ISequentialOutStream> outStream = new CFileOutStream(savePath); archive->Extract(&i, 1, 0, extractCallback); // 只解压第 i 个文件 }

这里CreateObject的参数是格式CLSID,对应源码里Register.cpp登记的GUID。如果你把其它格式都裁掉,只保留7z,这个CLSID可以直接硬编码,省掉整个格式探测逻辑。

6.2 把LZMA2当流压缩器用:内存到内存的压缩

7-Zip源码里LZMA编码器(CLzmaEnc_*)本身就是流式的,不需要依赖7z容器。可以直接创建编码器、配置属性、把输入内存流压缩到输出内存流。这里用到的API在DOC/lzma.txt有文档,是作者专门为集成者准备的一套C接口。

CLzmaEncHandle enc = LzmaEnc_Create(&alloc); CLzmaEncProps props; LzmaEncProps_Init(&props); // 初始化为默认参数 props.level = 9; props.dictSize = 64 << 20; // 64MB 字典 props.lc = 3; // 字面量上下文位 props.lp = 0; // 位置位 props.pb = 2; // posState 位 CLzmaEncProps_Normalize(&props); LzmaEnc_SetProps(enc, &props); // 输出前先把 5 字节属性头和 8 字节解压大小写出去 Byte header[13]; LzmaEnc_WriteProperties(enc, header); // 循环调用 LzmaEnc_Encode 处理缓冲,每次给一块输入 for (;;) { // src 是当前块指针,dest 是输出缓冲 SizeT srcLen = ...; // 本次输入块大小 SizeT destLen = ...; // 输出缓冲剩余空间 ELzmaStatus status; SRes res = LzmaEnc_Encode(enc, dest, &destLen, src, &srcLen, NULL, &status, callback); // status == LZMA_STATUS_FINISHED 表示输入全部处理完 }

这些API的参数注释很完整。需要注意一点:LzmaEnc_Encode在非FINISHED状态下返回时,destLen表示这次实际输出了多少字节,srcLen表示这次实际消费了多少字节,两者都不保证一次性完成。所以外层循环必须正确处理这两个返回值,否则会白白丢失数据。我自己做嵌入式日志压缩时,就是把它封装成一个"拉模式"的压缩器:上游不断喂数据,下游取压缩输出。核心收益是完全没有7z容器头的开销,适合私有归档协议。

6.3 怎么验证你改出来的东西没改坏

改完源码,最容易翻车的不是编译错误,而是"输出文件格式变了但你没发现"。我给自己定了一套验证流程:每次改动后用官方命令行跑一组固定测试集,对比压缩前后文件大小、解压后CRC以及压缩时间三者。测试集里会放三类文件:全零的稀疏文件(测LZMA对重复串的匹配)、纯文本日志(测熵编码效率)、一个5MB的随机二进制文件(测最差情况)。另外用一份标准的7z存档做解压测试,确认改动后的解压器能打开旧版源码压出来的文件——这个兼容性测试是把压缩格式做坏的最后一道防线。

在命令行里可以这样跑回归:

# 压缩测试集,生成同一命名规则的存档 ./7z a -t7z -mx=5 ./regress/test1.7z ./regress/data1 ./7z a -t7z -mx=9 ./regress/test2.7z ./regress/data2 # 解压后对每个文件做 CRC 校验 ./7z t ./regress/test1.7z ./7z t ./regress/test2.7z

如果对输出体积敏感的改动,我还会在改动前后各跑一遍同一个测试集,用diff对比清单文件里显示的"压缩前总大小/压缩后总大小",用数字确认回归是否存在。从那以后,我每次改完压缩相关代码都会强制走一遍这四件事:编译、压测试集、解压校验、对比体积。这个习惯救过我很多次,尤其是改过Range Coder位运算以后,一个小小的符号错误可能让解压器静默输出错误数据而不报错——如果不在压缩时同步核对CRC,你根本发现不了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 1:23:24

照片EXIF信息提取全攻略:从系统属性到Python脚本

简介&#xff1a;这是一份面向摄影爱好者、图像处理初学者及需要批量读取照片元数据用户的EXIF信息提取工具源码包&#xff0c;基于C#与.NET开发&#xff0c;可解决单张或批量图片拍摄参数读取、展示与导出的实际问题。压缩包共46个文件&#xff0c;约1.6MB&#xff0c;以cs源码…

作者头像 李华
网站建设 2026/10/19 1:21:46

AirLLM:单卡24G显存跑70B大模型的逐层加载方案

1. 单卡跑大模型这件事&#xff0c;到底卡在哪第一次看到 airllm 这个项目名的时候&#xff0c;我正蹲在一台只有单张 24G 显存的机器前发愁。手里有个 70B 级别的模型想跑推理&#xff0c;结果加载权重那一步就直接爆显存&#xff0c;连模型的门都没进去。相信不少做本地推理的…

作者头像 李华
网站建设 2026/10/19 1:21:44

GitHub与Cursor集成实战:用TaoToken统一Key打通AI辅助开发工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华