Zstandard 压缩库 C API 实战指南:从单文件压缩到流式与字典处理(zstd 1.5.7 示例精讲) 📅 发布时间:2026/9/18 13:42:32 👁 浏览次数: Zstandard 压缩库 C API 实战指南从单文件压缩到流式与字典处理zstd 1.5.7 示例精讲【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读本文以当前仓库中 vendored 的 zstd 1.5.7 源码包lib/zstd-1.5.7/examples/目录为骨架系统讲解 Zstandardzstd压缩库的 C 语言 API 使用方式。examples/README.md汇总了 9 个可独立编译运行的示例程序覆盖一次性压缩/解压、多文件上下文复用、流式压缩/解压、流式内存占用测量以及字典压缩/解压五大主题。读完本文你将掌握ZSTD_compress()、ZSTD_decompress()、ZSTD_compressStream2()、ZSTD_decompressStream()、ZSTD_createCDict()等核心 API 的调用模式与适用场景并了解 zstd 作为日志处理项目 Fluent Bit 压缩后端时的实际集成方式。一、示例程序全景一条由浅入深的学习路径lib/zstd-1.5.7/examples/README.md将 9 个示例按学习难度组织为一条清晰的路径每个示例对应一组核心 API表格如下示例源文件功能引入的核心 APIsimple_compression.c单文件压缩一次性ZSTD_compress()simple_decompression.c单文件解压一次性结果留在内存ZSTD_decompress()multiple_simple_compression.c一次命令行压缩多个文件演示通过复用已有资源最小化malloc()/free()调用的内存保持技巧ZSTD_compressCCtx()streaming_memory_usage.c输出流式上下文的内存占用ZSTD_sizeof_CStream()streaming_compression.c单文件流式压缩ZSTD_compressStream()multiple_streaming_compression.c一次命令行流式压缩多个文件演示通过复用现有资源降低malloc()/free()与memset()影响的技巧复用流式压缩 APIstreaming_decompression.c单文件流式解压兼容简单压缩与流式压缩产物结果输出到 stdoutZSTD_decompressStream()dictionary_compression.c使用同一字典压缩多个文件ZSTD_createCDict()、ZSTD_compress_usingCDict()dictionary_decompression.c使用同一字典解压多个文件结果留在内存ZSTD_createDDict()、ZSTD_decompress_usingDDict()值得注意的是示例目录中还存在一个 streaming_compression_thread_pool.cREADME 未在列表中展开但从目录结构看它与流式压缩主题相关进一步印证了该目录作为 zstd 官方 API 教学套件的定位。此外 Makefile 中all目标恰好编译 README 列出的 9 个程序与文档一一对应。二、构建示例Makefile 与公共辅助层所有示例共享两个基础设施一是lib/zstd-1.5.7/examples/Makefile中定义的构建规则二是 common.h 提供的错误检查与文件 I/O 辅助函数。2.1 编译命令从 Makefile 可以看到示例直接链接静态库$(LIBDIR)/libzstd.a其中LIBDIR ../lib头文件搜索路径为-I$(LIBDIR)。all目标依次构建全部 9 个示例程序$(LIB)依赖会先递归调用$(MAKE) -C $(LIBDIR) libzstd.a生成静态库。因此最简单的构建方式是cd lib/zstd-1.5.7/examples make每个示例程序都以common.h作为依赖项如simple_compression.o: common.h修改辅助层后会自动重编。make clean会删除所有目标文件、临时文件与生成的.zst压缩产物。2.2 common.h示例共用的“生存工具箱”common.h 提供了一组*_orDie辅助函数它们的共同特征是出错即打印错误信息并exit(1)让示例代码专注演示 zstd API 本身CHECK(cond, ...)条件不成立时输出文件、行号与条件文本并退出CHECK_ZSTD(fn)包装 zstd 返回值通过ZSTD_isError()与ZSTD_getErrorName()输出可读错误名common.h这是所有示例统一采用的错误处理范式fsize_orDie()、fopen_orDie()、fread_orDie()、fwrite_orDie()、malloc_orDie()分别封装文件大小获取、打开、读写与内存分配并定义COMMON_ErrorCode枚举区分错误类别mallocAndLoadFile_orDie()一次完成“分配内存 整文件读入内存”供一次性simple压缩/解压示例使用saveFile_orDie()将压缩结果写出到磁盘。理解这层封装后示例正文中每一处CHECK_ZSTD(...)实际都是在做“出错即退出”的严格校验这也是生产代码中处理 zstd 返回值的标准姿势。三、一次性simple压缩与解压最直接的 API 入口3.1 ZSTD_compress压缩单个文件simple_compression.c 演示了最简单的压缩流程核心调用位于 simple_compression.c 的compress_orDie()size_t fSize; void* const fBuff mallocAndLoadFile_orDie(fname, fSize); size_t const cBuffSize ZSTD_compressBound(fSize); /* 压缩后最大可能大小 */ void* const cBuff malloc_orDie(cBuffSize); size_t const cSize ZSTD_compress(cBuff, cBuffSize, fBuff, fSize, 1); /* level1 */ CHECK_ZSTD(cSize); saveFile_orDie(oname, cBuff, cSize);要点ZSTD_compressBound(fSize)返回“压缩结果绝不可能超过的上界”用于一次性安全分配输出缓冲区最后一个参数是压缩等级示例取1速度优先。zstd 的压缩等级范围约为 122等级越高压缩率越好、耗时越长实际取值需按数据特征权衡输出文件名由createOutFilename_orDie()在原文件名后追加.zst后缀simple_compression.c源码注释明确指出如果需要执行大量压缩操作应当复用上下文即下一个示例的主题而不是每次重新创建。命令行用法./simple_compression FILE # 生成 FILE.zst并打印 fname : 原大小 - 压缩后大小3.2 ZSTD_decompress解压回内存simple_decompression.c 是它的镜像。解压前先用ZSTD_getFrameContentSize()从帧头读取原始内容大小并检查两种特殊返回值unsigned long long const rSize ZSTD_getFrameContentSize(cBuff, cSize); CHECK(rSize ! ZSTD_CONTENTSIZE_ERROR, %s: not compressed by zstd!, fname); CHECK(rSize ! ZSTD_CONTENTSIZE_UNKNOWN, %s: original size unknown!, fname);ZSTD_CONTENTSIZE_ERROR输入根本不是合法的 zstd 帧ZSTD_CONTENTSIZE_UNKNOWN帧头未写入内容大小例如压缩时未预知源大小。注释给出的对策是改用流式解压或使用ZSTD_decompressBound()。随后调用ZSTD_decompress(rBuff, rSize, cBuff, cSize)一次性解压。由于 zstd 在已知内容大小时会自行校验解压结果是否匹配源码注释When zstd knows the content size, it will error if it doesnt match示例中的CHECK(dSize rSize, ...)实际是不可能触发的防御性断言。命令行用法./simple_decompression FILE.zst # 打印 fname : 压缩大小 - 原始大小并提示 correctly decoded (in memory)3.3 适用边界README 明确标注simple 模式只兼容 simple 模式产生的帧且解压结果保留在内存中。这意味着它天然不适合超大文件如数百 MB 级日志也不适合以管道/网络流形式到达的数据——这正是下一节流式 API 的用武之地。四、多文件一次性压缩用 ZSTD_compressCCtx 复用上下文multiple_simple_compression.c 解决一个真实工程问题连续压缩大量文件时反复malloc/free会造成性能抖动。它的思路是把输入缓冲区、输出缓冲区与压缩上下文全部预分配一次然后在循环中复用。其核心数据结构multiple_simple_compression.ctypedef struct { void* fBuffer; void* cBuffer; size_t fBufferSize; size_t cBufferSize; ZSTD_CCtx* cctx; } resources;createResources_orDie()会遍历所有输入文件找出最大文件名长度与最大文件大小据此一次性分配恰好够用的缓冲区并创建唯一的ZSTD_CCtx*。随后对每个文件执行size_t const cSize ZSTD_compressCCtx(ress.cctx, ress.cBuffer, ress.cBufferSize, ress.fBuffer, fSize, 1);ZSTD_compressCCtx()与ZSTD_compress()的参数几乎一致唯一区别是显式传入可复用的上下文对象从而省去每次压缩时上下文的创建与销毁开销。源码注释还提示如果需要更细粒度的参数控制应使用高级 APIZSTD_CCtx_setParameter()配合ZSTD_compress2()。命令行用法./multiple_simple_compression FILE1 FILE2 ... # 逐个生成 FILE1.zst、FILE2.zst ...五、流式压缩ZSTD_compressStream2 与帧结束指令5.1 缓冲区尺寸约定streaming_compression.c 用两个专用函数确定输入/输出缓冲大小size_t const buffInSize ZSTD_CStreamInSize(); /* 保证可读入完整一块 */ size_t const buffOutSize ZSTD_CStreamOutSize(); /* 保证可刷出完整一块 */源码注释强调缓冲区“可以是任意大小”但推荐用这两个函数取值因为“只有极小的缓冲区才会显著损失性能”——它们保证单次调用至少能消化一个完整的压缩块避免不必要的循环空转。5.2 参数设置压缩等级、校验和与多线程创建ZSTD_CCtx*后示例通过高级参数 API 进行配置streaming_compression.cZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, cLevel); ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); ZSTD_CCtx_setParameter(cctx, ZSTD_c_nbWorkers, nbThreads); /* nbThreads 1 时 */ZSTD_c_checksumFlag在帧尾写入校验和用于解压时检测数据损坏ZSTD_c_nbWorkers启用多线程压缩。示例对设置失败的场景做了降级处理——若链接的 libzstd 未编译多线程支持会打印提示并回退到单线程streaming_compression.c这是编写健壮代码值得借鉴的容错模式。5.3 主循环ZSTD_e_continue 与 ZSTD_e_end压缩主循环streaming_compression.c是理解流式压缩的关键size_t const toRead buffInSize; for (;;) { size_t read fread_orDie(buffIn, toRead, fin); int const lastChunk (read toRead); ZSTD_EndDirective const mode lastChunk ? ZSTD_e_end : ZSTD_e_continue; ZSTD_inBuffer input { buffIn, read, 0 }; int finished; do { ZSTD_outBuffer output { buffOut, buffOutSize, 0 }; size_t const remaining ZSTD_compressStream2(cctx, output, input, mode); CHECK_ZSTD(remaining); fwrite_orDie(buffOut, output.pos, fout); finished lastChunk ? (remaining 0) : (input.pos input.size); } while (!finished); if (lastChunk) break; }三个设计要点ZSTD_inBuffer/ZSTD_outBuffer结构分别用{ 指针, 总大小, 已消费/已产生位置 }描述输入与输出调用方通过pos字段推进结束指令语义非最后一块用ZSTD_e_continue继续当前帧最后一块用ZSTD_e_end收尾并关闭帧。源码注释特别指出若首个块即使用ZSTD_e_endzstd 会优化为“单趟压缩整个源数据”的路径循环退出条件ZSTD_compressStream2返回剩余工作量。最后一个块时返回 0 意味着“输入全部消费且帧已完成”中间块则以input.pos input.size作为完成判据。命令行用法./streaming_compression FILE [LEVEL] [THREADS] # 默认 LEVEL1、THREADS1例如 ./streaming_compression app.log 9 45.4 多文件流式压缩会话级复用multiple_streaming_compression.c 把上一节的循环封装成可复用的资源对象其核心技巧是ZSTD_CCtx_reset(cctx, ZSTD_reset_session_only)multiple_streaming_compression.c每次压缩新文件前将上下文重置为“干净会话”状态由于参数是“粘滞的”sticky之前在createResources_orDie()中设置的压缩等级与校验和标志会保留无需重复设置输入/输出缓冲区同样只分配一次跨文件复用从而把malloc()/free()与memset()的影响降到最低示例默认压缩等级为 7见 multiple_streaming_compression.c。六、流式解压兼容一切 zstd 帧直达 stdoutstreaming_decompression.c 展示了流式解压的完整形态其兼容性比 simple 模式更广——README 明确说明它同时兼容 simple 与 streaming 压缩的产物解压结果直接写入 stdout因此天然适合与管道如| cat配合。6.1 主循环与多帧处理size_t const buffInSize ZSTD_DStreamInSize(); size_t const buffOutSize ZSTD_DStreamOutSize(); /* 保证任意情况下都能刷出至少一个完整压缩块 */循环中每次读到数据就构建ZSTD_inBuffer并在input.pos input.size内反复调用ZSTD_decompressStream(dctx, output, input)将output.pos字节写入 stdout。关键语义streaming_decompression.c多帧自动衔接输入文件可以是多个 zstd 帧的简单拼接zstd 在一个帧完成时会自动重置上下文继续解析下一个帧以input.pos而非返回值判空源码注释解释zstd 在刷完某帧全部解压数据之前不会消费该帧最后一个字节因此用“输入是否消费完”作为内层循环条件更稳健ZSTD_DCtx_reset()的用途虽非必需但当上一次调用返回错误后显式重置上下文可使其恢复到干净状态。6.2 截断检测文件提前结束即是错误循环结束后示例检查最后一次调用的返回值streaming_decompression.cif (lastRet ! 0) { fprintf(stderr, EOF before end of stream: %zu\n, lastRet); exit(1); }若文件读完了但最后一个帧尚未结束lastRet ! 0说明输入被截断属于异常场景。空文件isEmpty也会被单独检出并报错。这两条边界处理让示例成为健壮流式解压的范本。命令行用法./streaming_decompression FILE.zst # 解压内容输出到 stdout ./streaming_decompression FILE.zst out.log # 重定向到文件七、流式上下文的内存占用测量ZSTD_sizeof_CStreamstreaming_memory_usage.c 是一份“内存预算”测量工具回答一个实际问题流式压缩/解压上下文到底占多少内存其引入的 API 是ZSTD_sizeof_CStream()配套使用的还有ZSTD_estimateCStreamSize_usingCCtxParams()、ZSTD_sizeof_DStream()与ZSTD_estimateDStreamSize_fromFrame()。程序对压缩等级 1 到MAX_TESTED_LEVEL默认 12可通过MAX_TESTED_LEVEL宏调整见 streaming_memory_usage.c逐级实测用ZSTD_createCCtxParams()保存参数集合设置压缩等级与ZSTD_c_windowLog0 表示使用默认窗口日志通过“不声明源大小”的方式强制压缩器按该等级的最大内存分配源码注释说明不提供 pledged source size 或直接以ZSTD_e_end调用都会触发最大内存路径分别读取实际占用ZSTD_sizeof_CStream(cctx)与预估ZSTD_estimateCStreamSize_usingCCtxParams(cctxParams)并断言实际 预估解压侧同理用ZSTD_estimateDStreamSize_fromFrame()依据压缩帧估算解压内存同样断言不超限。程序还支持K/M/KiB/MiB后缀的窗口大小参数如./streaming_memory_usage 21对应 windowLog21输出形如Level 1 : Compression Mem 263 KB (estimated : 263 KB) ; Decompression Mem 6 KB (estimated : 6 KB)这份示例的价值在于在嵌入式或内存受限场景中部署 zstd 之前可用它预先测定每个等级的内存足迹从而在压缩率与内存预算之间做出有依据的取舍。这与 Fluent Bit 这类对资源占用敏感的日志采集器高度相关。八、字典压缩与解压小样本数据的压缩率利器字典dictionary压缩适用于“许多小块、内容高度相似”的数据典型如日志行、JSON 事件、数据库记录通过一份预先训练的字典为小数据块提供上下文能显著提升压缩率。8.1 字典从哪来两个字典示例的源码注释都明确指出示例假定字典已经存在主流的生成途径有两种见 dictionary_compression.c命令行工具zstd --train基于样本集训练字典编程方式使用lib/zdict.h中发布的字典训练 API。8.2 字典压缩ZSTD_createCDict 与 ZSTD_compress_usingCDictdictionary_compression.c 先一次性加载字典并创建压缩字典对象void* const dictBuffer mallocAndLoadFile_orDie(dictFileName, dictSize); ZSTD_CDict* const cdict ZSTD_createCDict(dictBuffer, dictSize, cLevel); /* cLevel3 */ZSTD_createCDict()会在创建时完成字典的预处理与压缩之后对每个文件重复使用同一cdict避免重复加载开销。压缩调用为dictionary_compression.csize_t const cSize ZSTD_compress_usingCDict(cctx, cBuff, cBuffSize, fBuff, fSize, cdict);注释补充说明该调用会把字典 ID 与内容大小写入帧头但默认不写校验和如需控制这些选项应改用高级 API 组合ZSTD_CCtx_setParameter()ZSTD_CCtx_refCDict()ZSTD_compress2()。命令行用法./dictionary_compression FILE1 FILE2 ... dictionary # 最后一个参数是字典文件前面的每个 FILE 生成 FILE.zst8.3 字典解压ZSTD_createDDict 与 DictID 校验dictionary_decompression.c 对称地使用ZSTD_createDDict()创建解压字典对象。其独特之处在于显式的字典 ID 校验dictionary_decompression.cunsigned const expectedDictID ZSTD_getDictID_fromDDict(ddict); unsigned const actualDictID ZSTD_getDictID_fromFrame(cBuff, cSize); CHECK(actualDictID expectedDictID, DictID mismatch: expected %u got %u, ...);若使用非 zstd 训练出的字典两侧 ID 都会是 0校验自然通过zstd 默认会把字典 ID 写入帧头且解压时本身也会检查 ID 是否匹配——示例中的显式校验相当于把错误提示提前且更友好便于在日志中定位“用了错误字典解压”的问题。解压调用为ZSTD_decompress_usingDDict(dctx, rBuff, rSize, cBuff, cSize, ddict)结果保留在内存中与 simple 模式行为一致。命令行用法./dictionary_decompression FILE1.zst FILE2.zst ... dictionary九、示例的自动化验证Makefile 测试目标Makefile 内置的test目标把这些示例串成了完整的回归测试覆盖了 README 描述的各种组合简单模式先simple_compression再simple_decompression验证往返一致多文件压缩multiple_simple_compression *.c与multiple_streaming_compression *.c流式往返streaming_compression后用streaming_decompression解回内存测量直接运行streaming_memory_usage边界与负例对未压缩的普通文件执行解压! ./streaming_decompression tmp与! ./simple_decompression tmp必须失败对 0 字节文件执行压缩/解压必须成功——这两组用例恰好验证了前文提到的错误路径与空输入处理逻辑字典往返dictionary_compression tmp2 tmp README.md与dictionary_decompression tmp2.zst tmp.zst README.md。运行make test即可一次性验证 9 个示例的正确性这也是读懂每个示例行为最快的方式。十、在 Fluent Bit 中的实际集成zstd 不止于示例本文讨论的lib/zstd-1.5.7正是作为 Fluent Bit本仓库的 vendored 依赖被引入的。从仓库结构可以确认lib/zstd-1.5.7/ 是完整的 zstd 源码树通过 cmake/zstd.cmake 接入 Fluent Bit 的 CMake 构建体系核心封装位于 src/flb_zstd.c对应头文件 include/fluent-bit/flb_zstd.h为 Fluent Bit 上层如存储压缩、输出插件压缩提供统一的 zstd 压缩/解压入口。可以推断Fluent Bit 在构建时如果启用了 zstd 支持示例中展示的ZSTD_compress()/ZSTD_decompress()乃至流式 API 的调用模式会在flb_zstd.c中以封装函数的形式出现供诸如块压缩等模块复用。这也说明了示例代码的实际价值它们不仅是 API 教学更是生产集成的起点——开发者完全可以在阅读示例后参考 src/flb_zstd.c 的封装方式把同样的模式移植到自己的项目中。结语如何选择正确的 zstd API综合 9 个示例可以提炼出一条 API 选型决策线场景推荐 API示例小文件、一次性、已知大小ZSTD_compress/ZSTD_decompresssimple_compression / simple_decompression批量小文件、追求吞吐ZSTD_compressCCtx 复用缓冲区multiple_simple_compression大文件/管道/网络流ZSTD_compressStream2/ZSTD_decompressStreamstreaming_compression / streaming_decompression批量大文件、内存敏感流式 API ZSTD_CCtx_reset(session_only)复用multiple_streaming_compression海量相似小数据块ZSTD_createCDict/ZSTD_createDDict系列dictionary_compression / dictionary_decompression部署前内存预算评估ZSTD_sizeof_CStream/ZSTD_sizeof_DStreamstreaming_memory_usage无论选择哪条路径ZSTD_isError()ZSTD_getErrorName()的统一错误检查即CHECK_ZSTD模式都应当成为标配。以lib/zstd-1.5.7/examples/README.md为索引、以各.c示例为教材即可快速掌握 zstd 从入门到生产可用的全部核心用法。【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考