- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
导读:本文基于 KBEngine 仓库内置 Python 发行版中
lzma模块的官方文档(kbe/src/lib/python/Doc/library/lzma.rst)编写,系统讲解使用 LZMA 算法进行压缩/解压的完整 API,包括文件读写、内存一次性压缩、增量式压缩解压、三种容器格式(.xz、.lzma、RAW)与自定义滤镜链的实战用法。读完本文,你将掌握在 KBEngine 服务器脚本(kbe/res/scripts 下的 Python 资产)与内置 Python 环境中,如何借助lzma模块实现高压缩比的存档、日志与数据传输,并能读懂该模块在 Lib/lzma.py 与 Modules/_lzmamodule.c 中的底层实现。
一、模块定位:lzma 在 KBEngine 内置 Python 中的角色
lzma模块是 Python 标准库对liblzma压缩库的封装,提供了用于 LZMA 算法压缩与解压数据的类与便捷函数,并支持xz命令行工具所使用的.xz与旧版.lzma文件格式,以及原始(RAW)压缩流。该模块自 Python 3.3 引入(versionadded:: 3.3)。
在 KBEngine 仓库中,这个模块属于内置 Python 发行版的完整标准库一部分:
- 模块的 Python 层实现位于 Lib/lzma.py(共 347 行),其文件头注释明确写着 "Interface to the liblzma compression library";
- 底层的 C 扩展
_lzma实现在 Modules/_lzmamodule.c,直接#include <lzma.h>并调用liblzma的 C API; - 模块构建时会在 setup.py 中检测系统是否提供
lzma库:if self.compiler.find_library_file(lib_dirs, 'lzma'): exts.append(Extension('_lzma', ['_lzmamodule.c'], libraries=['lzma'])),若找不到liblzma,则把_lzma加入missing列表,此时该模块不可用——这提示我们在目标服务器上运行依赖lzma的脚本前,需要确认系统已安装liblzma开发库。
lzma模块提供的接口与bz2模块非常相似,但有一个重要区别:LZMAFile是非线程安全的(bz2.BZ2File则是线程安全的),因此如果需要在多线程中共享同一个LZMAFile实例,必须用锁保护。这一点在 Lib/lzma.py 和官方文档中均有明确说明。
模块通过__all__导出的完整 API 清单见 Lib/lzma.py,包括:
- 格式常量:
FORMAT_AUTO、FORMAT_XZ、FORMAT_ALONE、FORMAT_RAW; - 校验常量:
CHECK_NONE、CHECK_CRC32、CHECK_CRC64、CHECK_SHA256、CHECK_ID_MAX、CHECK_UNKNOWN; - 滤镜常量:
FILTER_LZMA1、FILTER_LZMA2、FILTER_DELTA以及FILTER_X86、FILTER_IA64、FILTER_ARM、FILTER_ARMTHUMB、FILTER_POWERPC、FILTER_SPARC等 BCJ(Branch-Call-Jump)滤镜; - 匹配查找器与模式常量:
MF_HC3、MF_HC4、MF_BT2、MF_BT3、MF_BT4、MODE_FAST、MODE_NORMAL、PRESET_DEFAULT、PRESET_EXTREME; - 类与函数:
LZMACompressor、LZMADecompressor、LZMAFile、LZMAError、open、compress、decompress、is_check_supported。
这些常量在 C 层由 Modules/_lzmamodule.c 的模块初始化函数PyInit__lzma通过PyModule_AddIntMacro/ADD_INT_PREFIX_MACRO逐一注入,并直接映射为liblzma头文件中的同名宏(如LZMA_CHECK_NONE、LZMA_FILTER_LZMA2等)。由于部分常量(如CHECK_SHA256)超过 32 位整型宽度,C 层专门提供了module_add_int_constant函数用PyLong_FromLongLong注入,以保证在 32 位long平台上取值正确。
二、异常与基础使用:LZMAError 与第一个压缩例子
2.1 LZMAError 异常
lzma.LZMAError在压缩、解压或初始化压缩器/解压器状态发生错误时抛出。在 C 层,它由 Modules/_lzmamodule.c 通过PyErr_NewExceptionWithDoc("_lzma.LZMAError", "Call to liblzma failed.", ...)创建,其异常消息直接来源于对liblzma调用返回码的映射(catch_lzma_error函数中可见诸如 "Unsupported integrity check"、"Compression failed" 等错误文案)。
2.2 快速上手:一次性压缩与解压
最简单的用法是调用lzma.compress(data)与lzma.decompress(data)两个便捷函数:
import lzma # 一次性压缩:默认使用 .xz 格式 data_in = b"Insert Data Here" data_out = lzma.compress(data_in) # 一次性解压:默认 FORMAT_AUTO,可自动识别 .xz 与 .lzma data_back = lzma.decompress(data_out) assert data_back == data_in从 Lib/lzma.py 的实现可以看到,compress()本质上是"创建一个LZMACompressor,调用一次compress(data)再调用flush(),然后拼接结果"的语法糖;而 decompress() 内部则是一个循环:反复创建LZMADecompressor解压当前流,把解出的数据累积到结果列表,再用unused_data继续处理后续流,直到没有剩余数据为止。
三、读写压缩文件:lzma.open 与 LZMAFile
3.1 lzma.open:二进制/文本双模式文件接口
函数签名:
lzma.open(filename, mode="rb", *, format=None, check=-1, preset=None, filters=None, encoding=None, errors=None, newline=None)要点说明:
filename可以是真实的文件名(str、bytes或 path-like 对象),此时会打开命名文件;也可以是已打开的文件对象,此时直接对其读写。mode在二进制模式下可以是"r"、"rb"、"w"、"wb"、"x"、"xb"、"a"、"ab"(默认"rb");文本模式则使用"rt"、"wt"、"xt"、"at"。- 读模式下,
format与filters的含义与LZMADecompressor一致,不应使用check与preset; - 写模式下,
format、check、preset、filters的含义与LZMACompressor一致。 - 二进制模式下该函数等价于
LZMAFile(filename, mode, ...)构造器,此时不能提供encoding、errors、newline参数;文本模式下,则会创建一个LZMAFile对象,再用io.TextIOWrapper按指定的编码、错误处理与换行方式包装。 - 历史变更:3.4 加入
"x"/"xb"/"xt"独占创建模式;3.6 起接受 path-like 对象。
这些约束在源码中有严格校验:见 Lib/lzma.py,二进制模式传入encoding/errors/newline会抛出ValueError。
3.2 LZMAFile:二进制文件接口
类签名:
class lzma.LZMAFile(filename=None, mode="r", *, format=None, check=-1, preset=None, filters=None)要点说明:
mode可为"r"(默认,读)、"w"(覆盖写)、"x"(独占创建)、"a"(追加),等价写法"rb"、"wb"、"xb"、"ab"。- 若
filename传入的是文件对象而非文件名,则"w"模式不会截断文件,行为等价于"a"追加。 - 读模式打开时,输入文件可以是多个独立压缩流的拼接,这些流会被透明地当作单一逻辑流解码。
- 读模式时
format/filters与LZMADecompressor语义相同,不使用check/preset;写模式时与LZMACompressor语义相同。 LZMAFile支持io.BufferedIOBase定义的全部成员,但detach()与truncate()除外;支持迭代与with语句。- 额外方法
peek(size=-1):返回缓冲中的数据而不推进文件位置;除非到达 EOF,否则至少返回一个字节,具体返回字节数未指定(size参数被忽略)。注意:调用peek不会改变LZMAFile的文件位置,但可能改变底层文件对象的位置(当LZMAFile是用文件对象构造时)。
从实现看,LZMAFile 继承自_compression.BaseStream,构造时:
- 读模式:用
_compression.DecompressReader把LZMADecompressor适配成 RawIOBase 读取器,再包一层io.BufferedReader; - 写模式:直接构造
LZMACompressor并在 write() 中把压缩输出写到底层文件对象; - close() 时会把压缩器
flush()的结果写入文件再关闭底层文件(若LZMAFile负责关闭它)。
_compression模块(Lib/_compression.py)同时被gzip、lzma、bz2三个模块共享,其中的DecompressReader为处理多流拼接与向后seek()会为每个流创建新的解压器对象。
3.3 官方示例:读写压缩文件
读取压缩文件:
import lzma with lzma.open("file.xz") as f: file_content = f.read()创建压缩文件:
import lzma data = b"Insert Data Here" with lzma.open("file.xz", "w") as f: f.write(data)向已打开的文件中写入压缩数据(混写场景):
import lzma with open("file.xz", "wb") as f: f.write(b"This data will not be compressed\n") with lzma.open(f, "w") as lzf: lzf.write(b"This *will* be compressed\n") f.write(b"Not compressed\n")3.4 文本模式的编码控制
文本模式("rt"/"wt"等)下,encoding、errors、newline三个参数直接透传给io.TextIOWrapper。仓库测试 Lib/test/test_lzma.py 中的test_encoding、test_encoding_error_handler、test_newline等用例验证了这些参数的行为,例如可以指定encoding="utf-8"、errors="replace"或自定义换行newline="\r\n"来读写文本型压缩存档。
四、内存中的压缩与解压:增量式 API 与一次性 API
4.1 LZMACompressor:增量压缩
类签名:
class lzma.LZMACompressor(format=FORMAT_XZ, check=-1, preset=None, filters=None)format:指定容器格式。可选值:FORMAT_XZ:.xz容器格式,默认值;FORMAT_ALONE:旧版.lzma容器格式,功能更受限——不支持完整性校验,也不支持多滤镜;FORMAT_RAW:不使用任何容器格式的原始数据流,不支持完整性校验,且压缩与解压都必须指定自定义滤镜链;此外,以 RAW 压缩的数据不能用FORMAT_AUTO解压。
check:指定压缩数据中携带的完整性校验类型,用于解压时确认数据未被损坏:CHECK_NONE:无校验,是FORMAT_ALONE与FORMAT_RAW的默认(也是唯一)可选值;CHECK_CRC32:32 位循环冗余校验;CHECK_CRC64:64 位循环冗余校验,是FORMAT_XZ的默认值;CHECK_SHA256:256 位安全散列算法。- 若指定的校验类型不受支持,抛出
LZMAError。
- 压缩设置二选一:通过
preset指定压缩级别,或通过filters指定自定义滤镜链。preset应为 0~9 的整数,可选与PRESET_EXTREME按位或;两者都未提供时默认使用PRESET_DEFAULT(即级别 6)。级别越高输出越小,但压缩越慢。- 注意:更高预设不仅更耗 CPU,还需要更多内存(解压时同样需要更多内存)。例如预设 9 时,一个
LZMACompressor对象的开销可高达 800 MiB,因此通常建议使用默认预设。
- 注意:更高预设不仅更耗 CPU,还需要更多内存(解压时同样需要更多内存)。例如预设 9 时,一个
- 方法:
compress(data):压缩data(bytes对象),返回包含至少部分输入压缩结果的bytes对象。部分data可能被内部缓冲,供后续compress()与flush()调用使用;返回数据应与之前所有compress()调用的输出拼接。flush():结束压缩过程,返回压缩器内部缓冲中的剩余数据。调用后压缩器不能再使用。
官方增量压缩示例:
import lzma lzc = lzma.LZMACompressor() out1 = lzc.compress(b"Some data\n") out2 = lzc.compress(b"Another piece of data\n") out3 = lzc.compress(b"Even more data\n") out4 = lzc.flush() # 拼接所有部分结果: result = b"".join([out1, out2, out3, out4])C 层实现要点(Modules/_lzmamodule.c):
- 使用
PyArg_ParseTupleAndKeywords解析参数;若format != FORMAT_XZ且check != -1且check != LZMA_CHECK_NONE,抛出ValueError("Integrity checks are only supported by FORMAT_XZ"); - 若同时提供
preset与filters,抛出ValueError("Cannot specify both preset and filter chain"); - 每个压缩器对象内部含有一把
PyThread_type_lock锁(ACQUIRE_LOCK/RELEASE_LOCK宏,见 Modules/_lzmamodule.c),说明 C 层为并发安全做了锁保护,但文档仍然声明高层LZMAFile非线程安全,需要调用方自行加锁; - 按格式分别调用
Compressor_init_xz(lzma_easy_encoder/lzma_stream_encoder)、Compressor_init_alone(lzma_alone_encoder)或Compressor_init_raw(lzma_raw_encoder+ 滤镜链解析); preset与filters均未指定时,preset的 C 层默认值为LZMA_PRESET_DEFAULT(Modules/_lzmamodule.c)。
4.2 LZMADecompressor:增量解压
类签名:
class lzma.LZMADecompressor(format=FORMAT_AUTO, memlimit=None, filters=None)format:默认FORMAT_AUTO,可自动解压.xz与.lzma文件;其他可选值FORMAT_XZ、FORMAT_ALONE、FORMAT_RAW。memlimit:以字节为单位限制解压器可使用的内存上限。指定后,若在给定内存限制内无法解压输入,解压将抛出LZMAError失败。filters:指定创建该流时所用的滤镜链。format为FORMAT_RAW时必须提供,其他格式不应使用。- 重要限制:该类不像
decompress()与LZMAFile那样透明处理包含多个压缩流的输入;要解压多流输入,必须为每个流创建新的解压器。 - 属性:
check:输入流使用的完整性校验 ID;在解码出足够输入以确定校验类型之前,可能是CHECK_UNKNOWN。eof:是否已到达流结束标记。unused_data:压缩流结束标记之后发现的数据;流结束前为b""。needs_input:若decompress()在需要新输入前还能产出更多解压数据,则为False。
- 方法
decompress(data, max_length=-1):- 解压
data(bytes-like 对象),返回解压后的字节;部分数据可能被内部缓冲供后续调用使用,返回数据应与此前所有调用结果拼接。 max_length非负时,最多返回max_length字节;若达到该限制且还能继续产出输出,needs_input会置为False,此时下次调用decompress()可传b''以取得更多输出。- 若全部输入数据都已解压并返回(无论因小于
max_length还是max_length为负),needs_input置为True。 - 到达流结束标记后再尝试解压会抛出
EOFError;流结束之后发现的数据被忽略并存入unused_data。
- 解压
C 层实现要点(Modules/_lzmamodule.c):
- 使用
LZMA_TELL_ANY_CHECK | LZMA_TELL_NO_CHECK解码标志,以便在输入未携带校验或校验未知时仍能获知信息(对应check属性); memlimit未指定时默认UINT64_MAX;FORMAT_RAW与memlimit同时指定会抛ValueError("Cannot specify memory limit with FORMAT_RAW");FORMAT_RAW未提供filters抛ValueError("Must specify filters for FORMAT_RAW");其他格式提供filters抛ValueError("Cannot specify filters except with FORMAT_RAW");- 按格式分别调用
lzma_auto_decoder、lzma_stream_decoder、lzma_alone_decoder或lzma_raw_decoder; check初始化为LZMA_CHECK_UNKNOWN,needs_input初始化为 1,unused_data初始化为空bytes;decompress_buf(Modules/_lzmamodule.c)中,每次lzma_code返回LZMA_GET_CHECK或LZMA_NO_CHECK时通过lzma_get_check更新check属性,返回LZMA_STREAM_END时置eof = 1。
4.3 一次性便捷函数
lzma.compress(data, format=FORMAT_XZ, check=-1, preset=None, filters=None) lzma.decompress(data, format=FORMAT_AUTO, memlimit=None, filters=None)compress(data):压缩data(bytes),返回压缩结果bytes;参数含义与LZMACompressor相同。decompress(data):解压data(bytes),返回解压结果;若data是多个独立压缩流的拼接,则全部解压并返回结果的拼接;参数含义与LZMADecompressor相同。
一次性解压多流、以及鲁棒处理压缩流之后的非 LZMA 垃圾数据,均有对应的仓库测试用例:test_decompress_multistream 验证lzma.decompress(COMPRESSED_XZ + COMPRESSED_ALONE)能完整还原两段输入;test_decompress_trailing_junk与test_decompress_multistream_trailing_junk验证压缩流后附带垃圾字节时仍能正确解压出有效部分。
五、自定义滤镜链:精细控制压缩行为
5.1 滤镜链规格(Filter Chain Specifier)
滤镜链规格是字典的序列,每个字典包含单个滤镜的 ID 与选项。每个字典必须包含"id"键,可包含额外的滤镜相关选项键。合法的滤镜 ID 如下:
- 压缩滤镜:
FILTER_LZMA1(用于FORMAT_ALONE)FILTER_LZMA2(用于FORMAT_XZ与FORMAT_RAW)
- Delta 滤镜:
FILTER_DELTA
- BCJ(分支-调用-跳转)滤镜:
FILTER_X86FILTER_IA64FILTER_ARMFILTER_ARMTHUMBFILTER_POWERPCFILTER_SPARC
约束:滤镜链最多包含 4 个滤镜,且不能为空;链中最后一个滤镜必须是压缩滤镜,其余滤镜必须是 delta 或 BCJ 滤镜。
5.2 压缩滤镜选项
压缩滤镜支持以下选项(作为字典中的额外键指定):
preset:压缩预设,为未显式指定的选项提供默认值来源;dict_size:字典大小(字节),应在 4 KiB 到 1.5 GiB(含)之间;lc:字面上下文位数(literal context bits);lp:字面位置位数(literal position bits),要求lc + lp之和不超过 4;pb:位置位数(position bits),最大为 4;mode:MODE_FAST或MODE_NORMAL;nice_len:匹配的"理想长度",应不超过 273;mf:匹配查找器,可选MF_HC3、MF_HC4、MF_BT2、MF_BT3、MF_BT4;depth:匹配查找器的最大搜索深度,0(默认)表示根据其他滤镜选项自动选择。
5.3 Delta 与 BCJ 滤镜选项
- Delta 滤镜存储字节之间的差值,在某些场景下为压缩器制造更重复的输入。仅支持一个选项
dist:被减字节之间的距离,默认 1,即取相邻字节的差值。 - BCJ 滤镜用于机器码:将代码中的相对分支、调用与跳转转换为绝对寻址,从而增加压缩器可利用的冗余度。仅支持一个选项
start_offset:映射到输入数据起始处的地址,默认 0。
5.4 官方示例:使用自定义滤镜链
import lzma my_filters = [ {"id": lzma.FILTER_DELTA, "dist": 5}, {"id": lzma.FILTER_LZMA2, "preset": 7 | lzma.PRESET_EXTREME}, ] with lzma.open("file.xz", "w", filters=my_filters) as f: f.write(b"blah blah blah")该示例展示了"Delta 预处理器 + LZMA2 主压缩器"的经典组合:先用dist=5的 Delta 滤镜预处理数据,再用preset=7配合PRESET_EXTREME的 LZMA2 滤镜做实际压缩。
从 C 层看,滤镜链由parse_filter_chain_spec解析为lzma_filter[]数组(LZMA_FILTERS_MAX + 1个槽位),并对FORMAT_RAW直接调用lzma_raw_encoder/lzma_raw_decoder(见 Modules/_lzmamodule.c 与 L1090-L1103)。仓库测试 Lib/test/test_lzma.py 的test_bad_filter_spec系列验证了非法滤镜规格(缺id键、未知id、未知选项键等)会抛出TypeError/ValueError;test__encode_filter_properties则验证dist超出合法范围(如 9001)会触发LZMAError。
六、杂项工具:is_check_supported
函数签名:
lzma.is_check_supported(check)返回给定完整性校验类型在当前系统上是否受支持。CHECK_NONE与CHECK_CRC32总是受支持;CHECK_CRC64与CHECK_SHA256在liblzma以受限特性集编译时可能不可用。
因此,在跨服务器部署 KBEngine 时,若代码依赖CHECK_SHA256,建议先调用is_check_supported做运行时探测,避免在特性受限的liblzma上抛出LZMAError。仓库测试 test_is_check_supported 印证了文档结论:CHECK_NONE与CHECK_CRC32恒为真,而CHECK_ID_MAX + 1与CHECK_UNKNOWN恒为假。
七、多流拼接行为:三套 API 的差异速查
这是最容易踩坑的知识点,官方文档与源码、测试三方相互印证:
| API | 是否透明处理多个拼接的压缩流 | 依据 |
|---|---|---|
LZMAFile(读模式) | 是,透明解码为单一逻辑流 | Lib/lzma.py 经_compression.DecompressReader处理;测试 test_read_multistream 等 |
lzma.decompress() | 是,解压全部流并拼接结果 | Lib/lzma.py 的循环实现;测试 test_decompress_multistream |
LZMADecompressor | 否,遇到流结束标记后剩余数据存入unused_data,需为每个流新建解压器 | 测试 test_decompressor_multistream 明确断言unused_data中保存第二个流 |
其中LZMADecompressor的行为在测试中非常直观:LZMADecompressor()解压COMPRESSED_XZ + COMPRESSED_ALONE后,unused_data恰为COMPRESSED_ALONE(Lib/test/test_lzma.py)。
八、内存与安全性注意事项
- 高预设的内存开销:
preset=9时单个LZMACompressor对象开销可高达约 800 MiB(官方文档明确警告),对 KBEngine 服务器这种长期运行的进程尤其要谨慎,避免在热点路径上使用高预设,或对解压器使用memlimit防止内存失控。 - 多线程使用
LZMAFile需加锁:官方文档明确LZMAFile非线程安全(与bz2.BZ2File不同)。虽然 C 层每个压缩器/解压器内部各有锁(见 Modules/_lzmamodule.c),但高层的文件对象接口仍需调用方自行串行化。 - 解压炸弹防护:
memlimit与LZMADecompressor.decompress(data, max_length)是控制解压侧资源消耗的主要手段;LZMAFile的内部缓冲限制也有专门测试保护(test_decompress_limited 验证解压出的数据不会超过1 + DEFAULT_BUFFER_SIZE的缓冲上限)。 - 文本模式参数冲突:二进制模式下传
encoding/errors/newline会抛ValueError;"rt"等文本模式才会启用这些参数(Lib/lzma.py)。 - RAW 格式的约束:
FORMAT_RAW压缩与解压都必须显式提供相同滤镜链,无法用FORMAT_AUTO自动探测,也不支持完整性校验与memlimit。
九、实战建议:在 KBEngine 场景中的应用
在 KBEngine 项目中,lzma模块最常见的落地场景包括:
- 存档与备份压缩:在服务器脚本中把实体数据库导出内容、日志批次写入
.xz文件,利用preset在压缩率与 CPU/内存之间权衡——日常归档用默认PRESET_DEFAULT,冷数据归档可尝试PRESET_EXTREME; - 跨进程/跨机传输前的预压缩:对体积较大的消息载荷先
lzma.compress,接收端lzma.decompress还原;由于decompress()自动处理多流拼接,分片传输后拼接再解压也安全; - 文本配置与脚本资源打包:使用
lzma.open("xxx.xz", "wt", encoding="utf-8")直接读写文本型资源,借助io.TextIOWrapper的换行与编码控制保持跨平台一致性。
需要提醒的是:在调用任何依赖_lzma的代码前,请确认目标机器已安装liblzma(构建时 setup.py 依赖系统库),否则import lzma会失败;同时建议通过lzma.is_check_supported探测高版本校验类型的可用性。
十、参考索引
- 模块文档(本文主题):kbe/src/lib/python/Doc/library/lzma.rst
- Python 层实现:kbe/src/lib/python/Lib/lzma.py
- C 扩展实现:kbe/src/lib/python/Modules/_lzmamodule.c
- 共享压缩基础设施:kbe/src/lib/python/Lib/_compression.py
- 完整测试套件:kbe/src/lib/python/Lib/test/test_lzma.py
- 构建检测逻辑:kbe/src/lib/python/setup.py
- KBEngine 服务器脚本目录:kbe/res/scripts
- 游戏开发
- 后端
【免费下载链接】kbengine
A MMOG engine of server.
相关推荐
CMake FindLibLZMA 模块详解:在项目中集成 LZMA/XZ 压缩库
CMake FindLibLZMA 模块详解:在项目中集成 LZMA/XZ 压缩库 导读 本篇文章围绕 CMake 官方仓库中的 FindLibLZMA 查找模
构建工具开发工具CLILZMA-JS: 在浏览器中实现高效的LZMA压缩和解压缩算法
LZMA JS: 在浏览器中实现高效的LZMA压缩和解压缩算法 本文将为您详细介绍LZMA JS项目,并分享其主要功能、应用场景和亮点特色。LZMA JS是一个
UEFITool中的压缩算法支持:LZMA和Tiano压缩技术详解
UEFITool中的压缩算法支持:LZMA和Tiano压缩技术详解 UEFITool是一款专业的UEFI固件图像查看器和编辑器,它支持多种压缩算法来高效处理固件
固件逆向工程开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考