资讯动态

zstd Seekable 格式:可随机访问压缩档案的字节级布局与源码实现解析

发布时间:2026/9/19 13:45:42 来源:尧图企业网站定制
zstd Seekable 格式可随机访问压缩档案的字节级布局与源码实现解析【免费下载链接】zstdZstandard - Fast real-time compression algorithm项目地址: https://gitcode.com/gh_mirrors/zs/zstdzstd 的 seekable format 允许将压缩数据切分为若干相互独立的 frame并在文件尾部追加一张“seek table”使解码器能够直接跳转到目标区段而无需解压整个档案。本篇以仓库中的格式规范 zstd_seekable_compression_format.md 为主体逐字段讲解该格式的二进制布局、位域约定与校验规则并结合 contrib/seekable_format 目录下的参考实现zstdseek_compress.c、zstdseek_decompress.c与示例程序说明压缩、随机读、并行构建等实际使用方式。读完本文你可以完整理解 seek table 帧的每个字段含义并会用ZSTD_seekable_*API 构建和随机读取 seekable 档案。一、什么是 Seekable Format设计动机与整体思路标准 zstd 流中后续 block 的解码通常依赖前文上下文match 可能跨越文件前部。因此若要获取档案中间一小段数据往往必须从头解压。Seekable 格式通过两个机制解决这一问题数据切帧输入数据被切分为多个 frame每个 frame 独立压缩、独立解码因此可以只解压目标 frameseek table跳转表在文件末尾附加一张表记录每个 frame 的压缩后大小、解压后大小及可选校验和解码器据此 O(log N) 定位目标 frame。从 README 的描述看帧是顺序追加的因此整个负载按顺序解压后仍能还原原始内容而 seek table 存放在一个Zstandard skippable frame可跳过帧中对不认识该格式的标准 zstd 解码器而言会被直接跳过、完全不影响解码这正是该格式“向后兼容”的关键。一个完整的 seekable 文件结构为[ frame 0 ] [ frame 1 ] ... [ frame N-1 ] [ seek table skippable frame ]其中前 N 个是普通 zstd 压缩帧也允许 skippable/空帧其Decompressed_Size记 0最后一个 skippable 帧内容即 seek table。二、通用约定格式规范开头给出了三条书写约定阅读下文所有字段时应注意方括号[...]表示该字段是可选的例如[Checksum]、[Seek_Table_Entries]标识符命名约定为Mixed_Case_With_Underscores所有数值字段默认小端序little-endian除非另有说明。这些约定在参考实现中同样成立写入侧使用MEM_writeLE32写 32 位字段见 zstdseek_compress.c 中的ZSTD_stwrite32读取侧使用MEM_readLE32解析见 zstdseek_decompress.c。三、Seek Table 帧的完整布局Seek table 本身是一个标准 zstd skippable frame其内部结构为Skippable_Magic_NumberFrame_Size[Seek_Table_Entries]Seek_Table_Footer4 字节4 字节每条 812 字节9 字节3.1 Skippable_Magic_Number值0x184D2A5E用于兼容 zstd 可跳过帧规范。规范明确指出由于其他 zstd skippable 帧合法地可以使用同一个 magic number不建议解码器仅凭该 magic 就认定这是一个 seek table 帧——必须结合帧末尾的Seekable_Magic_Number来确认。从源码看写入侧生成的 magic 是ZSTD_MAGIC_SKIPPABLE_START | 0xE即0x184D2A5E见 zstdseek_compress.c读取侧则同时校验文件头处的 skippable magic 与末尾的 seekable magic见 zstdseek_decompress.c。3.2 Frame_Size该 skippable 帧的总大小不含Skippable_Magic_Number与Frame_Size字段本身同样用于兼容 zstd skippable frame 规范。对应实现中Frame_Size seekTableLen - ZSTD_SKIPPABLEHEADERSIZE即“表条目 9 字节 footer”的总长zstdseek_compress.c。3.3 Seek_Table_Footer9 字节footer 位于 seek table 帧末尾也是整个文件的最后 9 字节Number_Of_FramesSeek_Table_DescriptorSeekable_Magic_Number4 字节1 字节4 字节Seekable_Magic_Number值为0x8F92EAB1即 zstd_seekable.h 中的ZSTD_SEEKABLE_MAGICNUMBER。规范要求它必须是压缩文件最后存在的字节序列这样解码器可以一次seek到文件末尾读 9 字节低成本地判断文件是否带有 seek table。参考实现的ZSTD_seekable_loadSeekTable正是先SEEK_END回退 9 字节读取 footer并检查偏移 5 处是否为该 magic不匹配则返回prefix_unknown错误zstdseek_decompress.c。Number_Of_Frames数据中包含的 frame 数量不包括 seek table 帧自身。Seek_Table_Descriptor一个描述表格式的位域bitfield位号字段名7Checksum_Flag6–2Reserved_Bits1–0Unused_BitsChecksum_Flagbit 7置位时表示每条Seek_Table_Entry额外包含 4 字节校验和条目长度从 8 字节变为 12 字节Reserved_Bitsbit 6–2当前未使用但保留给未来的破坏性变更例如引入内嵌字典。合规的解码器应当校验这些位必须为 0否则报损坏。这一点在读取实现中被严格执行if ((sfd 2) 0x1f) return ERROR(corruption_detected);zstdseek_decompress.cUnused_Bitsbit 1–0留给未来的非破坏性变更解码器不应解释这些位。footer 中另有一个 9 字节常量ZSTD_seekTableFooterSize在 zstd_seekable.h 中定义与规范的 4149 完全一致。3.4 Seek_Table_Entries每条 8 或 12 字节条目共有Number_Of_Frames个按 frame 顺序0 到 N-1排列不含 seek table 帧本身。每条格式为Compressed_SizeDecompressed_Size[Checksum]4 字节4 字节4 字节Compressed_Size该 frame 的压缩后大小。规范的巧妙之处在于frame 0 到 i 的Compressed_Size累加和恰好等于 frame i1 在压缩文件中的偏移——条目本身不存偏移而是存“宽度”解码器在加载表时做一次前缀和即可得到每个 frame 的绝对偏移参考实现即如此见 zstdseek_decompress.c 中cOffset/dOffset的累计逻辑。Decompressed_Size该 frame 内解压后数据的大小对于 skippable 帧或空帧该值为 0。Checksum仅当Checksum_Flag置位时存在值为该 frame 解压数据的XXH64 摘要的最低 32 位小端存储。这与写入侧代码XXH64_digest(zcs-xxhState) 0xFFFFFFFFUzstdseek_compress.c一致读取侧在解完一个 frame 后同样计算并比对不匹配则返回corruption_detectedzstdseek_decompress.c。四、从实现角度看格式的三个关键细节4.1 帧大小上限1 GiB 与 21 亿帧规范中Compressed_Size/Decompressed_Size均为 4 字节无符号数因此单帧大小天然受 2^32 限制。zstd_seekable.h 中定义了两个约束#define ZSTD_SEEKABLE_MAXFRAMES 0x8000000U /* Limit maximum size to avoid potential issues storing the compressed size */ #define ZSTD_SEEKABLE_MAX_FRAME_DECOMPRESSED_SIZE 0x40000000U帧数上限为0x800000021,474,836 帧ZSTD_seekable_logFrame在超限时返回frameIndex_tooLarge错误单帧解压后大小上限为0x400000001 GiBZSTD_seekable_initCStream对超出的maxFrameSize直接拒绝并返回frameParameter_unsupported错误zstdseek_compress.c。4.2maxFrameSize的自动切帧机制ZSTD_seekable_initCStream(zcs, compressionLevel, checksumFlag, maxFrameSize)的第四个参数决定切帧粒度maxFrameSize 0时使用默认上限。从 zstdseek_compress.c 的ZSTD_seekable_compressStream实现看每次调用先将被消费长度钳制在maxFrameSize - frameDSize以内喂给底层ZSTD_compressStream当本帧解压字节数达到maxFrameSize时自动调用ZSTD_seekable_endFrame结束当前帧、记录帧日志并复位会话ZSTD_reset_session_only保证下一帧不依赖上一帧上下文——这是“各帧可独立解码”在实现上的落点。如何选取maxFrameSizeREADME 给出了明确建议帧越小随机读取小段数据时成本越低因为取 1 个字节也必须解压它所在整帧经验法则是让最大帧大小与已知的访问粒度同量级——例如应用倾向于请求 4 KB 块就把帧大小设在 4 KB 附近但帧过小会同时降低压缩率并增大 seek table 开销每帧固定 8 或 12 字节条目需要权衡一般应避免过小的帧 1 KB对压缩率伤害明显。4.3 随机定位二分查找与“帧前缀丢弃”ZSTD_seekable_offsetToFrameIndex对 seek table 做二分查找找出解压偏移 pos的最后一个 framezstdseek_decompress.c使定位复杂度为 O(log N)。ZSTD_seekable_decompress(zs, dst, len, offset)的调用流程为二分找到目标 frameseek到该 frame 的压缩偏移处若目标偏移不在 frame 头部则先把 frame 前缀解压到内部丢弃缓冲outBuff直到推进到目标偏移再写入用户缓冲若连续多次调用请求连续区段实现会保留ZSTD_DStream会话zs-curFrame/decompressedOffset避免重复解压帧前缀为防止损坏数据导致的死循环连续 16 次ZSTD_SEEKABLE_NO_OUTPUT_PROGRESS_MAX无输出进展即返回seekableIO错误zstdseek_decompress.c。五、压缩侧实战流式 API 与示例仓库提供完整示例 examples/seekable_compression.c用法为./seekable_compression FILE FRAME_SIZE [LEVEL]压缩级别缺省为 5核心调用序列如下ZSTD_seekable_CStream* cstream ZSTD_seekable_createCStream(); ZSTD_seekable_initCStream(cstream, cLevel, 1 /* checksumFlag */, frameSize); /* 循环喂入数据返回值为输入提示值input.pos 可能 input.size需续喂 */ while (input.pos input.size) { ZSTD_outBuffer output { buffOut, buffOutSize, 0 }; toRead ZSTD_seekable_compressStream(cstream, output, input); /* 将 output 已写部分落盘 */ } /* 结束先收尾当前帧再写 seek table 返回 0 表示 output 缓冲区不足需再次调用直至返回 0 */ while (1) { ZSTD_outBuffer output { buffOut, buffOutSize, 0 }; size_t const remaining ZSTD_seekable_endStream(cstream, output); /* 写出 outputremaining 0 时 break */ } ZSTD_seekable_freeCStream(cstream);要点说明均来自 zstd_seekable.h 的 HowTo 注释checksumFlag为 1 时seek table 中每个 frame 会附带其解压数据的校验和用于读取时验证ZSTD_seekable_endStream会先结束当前 frame、再写 seek table若 output 缓冲区装不下返回剩余字节数应重复调用直到返回 0流对象可复用再次压缩前调用ZSTD_seekable_initCStream即可避免重复分配。六、解码侧实战三种初始化模式ZSTD_seekable对象提供三种初始化入口zstd_seekable.h函数适用场景说明ZSTD_seekable_initBuff(zs, src, srcSize)内存缓冲src必须包含整个 seekable 文件含 seek table且在对象释放/重置前必须保持存活且不被修改ZSTD_seekable_initFile(zs, FILE*)文件stdio内部使用fread/fseekFILE*在释放/重置前不应关闭或修改ZSTD_seekable_initAdvanced(zs, customFile)自定义 I/O用户提供read必须恰好读满 n 字节提前 EOF 视为错误与seek支持SEEK_SET/SEEK_END回调成功返回非负、失败返回负值文档同时提醒基于 stdio 实现时注意 4 GB 文件与fseek的限制示例程序 examples/seekable_decompression.c 演示了从START到END区段的随机读取ZSTD_seekable* seekable ZSTD_seekable_create(); ZSTD_seekable_initFile(seekable, fin); while (startOffset endOffset) { size_t const result ZSTD_seekable_decompress( seekable, buffOut, MIN(endOffset - startOffset, buffOutSize), startOffset); /* 写出 result 字节startOffset result */ } ZSTD_seekable_free(seekable);除按字节偏移解压外还提供ZSTD_seekable_decompressFrame(zs, dst, dstSize, frameIndex)按帧索引整帧解压以及一组表访问函数ZSTD_seekable_getNumFrames、getFrameCompressedOffset、getFrameDecompressedOffset、getFrameCompressedSize、getFrameDecompressedSize、offsetToFrameIndex。注意越界语义的差异越界的索引访问函数如 getNumFrames 系列的 size 查询返回可用ZSTD_isError()判定的错误码而返回unsigned long long的偏移查询函数越界时返回哨兵值ZSTD_SEEKABLE_FRAMEINDEX_TOOLARGE0ULL-2。七、Raw Seek Table API 与并行压缩对于希望“帧并行独立压缩、事后汇总”的场景多线程或分布式规范配套了 Raw seek table APIzstd_seekable.hZSTD_seekable_createFrameLog(checksumFlag)创建一个帧日志checksumFlag 为 0 时传入的 checksum 将被忽略每压好一个帧调用一次ZSTD_seekable_logFrame(fl, compressedSize, decompressedSize, checksum)全部帧落盘后ZSTD_seekable_writeSeekTable(fl, output)将日志序列化为 seek table即第三节的 skippable 帧追加到帧文件末尾即可。若输出缓冲区不足返回值为剩余待写字节数可续写。examples/parallel_compression.c 完整演示了该模式将输入按frameSize切片提交线程池POOL_add每帧独立调用ZSTD_compress并计算XXH64校验和由于各线程乱序完成用一把互斥锁维护“按 id 顺序刷盘”的 pending 链保证文件内帧顺序正确、ZSTD_seekable_logFrame按序记录最后循环调用ZSTD_seekable_writeSeekTable把表追加到文件尾。该示例要求多线程版本 libzstdexamples/Makefile 中针对并行工具链接libzstd.a-mt。八、独立 Seek Table 管理与验证ZSTD_seekTable内存受限、需要同时缓存多份档案索引的场景下可以ZSTD_seekTable_create_fromSeekable从ZSTD_seekable中摘出较小的ZSTD_seekTable随即释放ZSTD_seekable本体之后仅凭 seek table 的偏移信息配合标准 zstd 解码即可取帧。它提供与ZSTD_seekable_*同构的一整套查询函数ZSTD_seekTable_getNumFrames等zstd_seekable.h。单元测试tests/seekable_tests.c 验证了基本的压缩—加载—随机读回环包括 4 KB 数据压缩后ZSTD_seekable_initBuff加载、整帧解回、以及从ZSTD_seekable导出ZSTD_seekTable后断言第 0 帧偏移为 0 等表查询行为。模糊测试tests/fuzz/seekable_roundtrip.c 对 seekable 格式做随机读写回环 fuzz是格式健壮性的持续保障。九、版本变更与兼容性说明格式规范当前版本为0.1.02017-04-11 初始版本Version Changes 记录如下0.1.0初始版本。与 zstd 主格式的兼容性边界可以概括为任何不认识 seekable 格式的 zstd 解码器会将末尾的 seek table 当作普通 skippable 帧跳过只解出前 N 个数据帧的内容合规的 seekable 解码器不能只靠0x184D2A5E判定帧类型其他 skippable 帧可合法使用该 magic应以文件末尾 4 字节是否为0x8F92EAB1为最终判据并校验 descriptor 中的Reserved_Bits全为 0所有 32 位字段小端序帧数受ZSTD_SEEKABLE_MAXFRAMES0x8000000约束单帧解压大小受ZSTD_SEEKABLE_MAX_FRAME_DECOMPRESSED_SIZE0x400000001 GiB约束超出时 API 返回错误而非静默截断。综合来看这份格式规范与 contrib/seekable_format 参考实现是一一对应的规范定义字节布局与合规解码器的校验义务实现则把“magic 校验、保留位检查、前缀和偏移、二分定位、帧前缀丢弃、进度保护”逐条落实可直接作为第三方实现的对照基准。【免费下载链接】zstdZstandard - Fast real-time compression algorithm项目地址: https://gitcode.com/gh_mirrors/zs/zstd创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价