Repository navigation
fix: libimebin 导出的 *.dict 与 libime/Fcitx5 逐字节一致 - #429
Merged
Merged
Conversation
libime 的 writeZSTDCompressed 并非直接调用 zstd,而是走 Boost.Iostreams 过滤器链 (128B 外层缓冲 + 4096B symmetric_filter + ZSTDCompressor)。symmetric_filter::close() 用空输入反复 filter(flush=true) 直到缓冲排空;当关闭时 zstd 内部积压超过一个缓冲时, 循环会多跑一轮:这一轮的 ZSTD_compressStream 写完当前帧剩余输出,紧接的 ZSTD_endStream 便新开并立即结束一个空帧,于是帧尾多出 13 字节(28 b5 2f fd 24 00 01 00 00 99 e9 d8 51)。 此前用 CompressionStream 直连 zstd 会漏掉该帧,导致大词库比 libime_pinyindict / Fcitx5 产物少 13 字节、哈希不一致;小词库(压缩后不足一个缓冲)不触发,所以此前只在部分大词库上暴露。 空帧是否出现取决于关闭时刻的积压量,并非简单的大小阈值(随机数据在 128KiB 整数倍时同样不出现)。 新增 LibimeZstdWriter 逐字节复刻该链路(deflate 的 eof 语义、close 的多轮 flush 循环、 双层缓冲),底层改用 ZstdSharp.Unsafe 的原生流式 API;LibimeDictFormat.Write 改用它。 验证: - 与真实上游文件做容器层往返(解压 → 读入 trie → 重新写出),要求逐字节一致: 测试/libime、测试/小企鹅 的 ceshi-4.dict = 24696 A6BA7CED…; 小企鹅/cpdf2.dict(25MB)= 25544344 CAC84C5F…;小词库 1352/1385/1429/1439 无空帧 - 与真实 Boost 1.86 + libzstd 1.5.7 搭出的参考实现对比:12 种载荷 × 5 种写入分片共 50 组 全部逐字节一致(覆盖 4096 边界、128KiB 整数倍、52MB 载荷) - 新增 3 个回归测试;全量测试 454 项:451 通过 / 3 手动跳过 / 0 失败
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
libimebin(Fcitx5 / libime 二进制拼音词库,容器 version0x2)导出的*.dict与libime_pinyindict/ Fcitx5 写出的同内容文件不字节一致:即「用官方工具转换」和「用本工具转换」得到的词库对不上哈希,需要逐字节复现上游产物的流程无法通过。
原因
libime 的
writeZSTDCompressed()不是一次普通的 zstd 压缩调用,而是一条 Boost.Iostreams 过滤链:关闭时
symmetric_filter::close()会用空输入反复调用filter(flush=true),直到 4096B 输出缓冲能排空。如果关闭那一刻 zstd 内部积压的输出超过一个缓冲,循环就要多跑一轮:这一轮的ZSTD_compressStream把当前帧的剩余输出写完,紧接着的ZSTD_endStream于是新开并立即结束了一个空帧,文件尾部因此多出 13 字节:关键点:是否出现该空帧取决于数据(取决于关闭时刻 zstd 内部的积压量),不是固定的大小阈值——小词库不会出现,大词库会出现。所以不能简单「在文件末尾补 13 字节」,必须复刻同一套缓冲与 flush 循环。
改动
LibimeZstdWriter(src/ImeWlConverter.Formats/LibIMEPinyin/LibimeZstdWriter.cs):通过ZstdSharp.Unsafe直接驱动 libzstd 流式 API,逐字节复刻上面这条链路——128B 外层缓冲、4096B 过滤器缓冲、write/close的 flush 循环、ZSTD_initCStream(0)+ZSTD_c_checksumFlag=1。LibimeDictFormat.Write改用它替代原来的CompressionStream一次性压缩。0x000fc613、version0x2),导入路径不受影响(仍支持 legacy0x1未压缩文件)。验证
新增 3 个单元测试(
LibIMEPinyinTest):Export_ReproducesUpstreamLibimeBytesExport_LargeDict_EndsWithLibimeTrailingEmptyFrameExport_SmallDict_HasNoTrailingEmptyFrame真实词库回归:
该哈希与
libime_pinyindict/ Fcitx5 产出的参考文件逐字节相同,且libimebin → libimebin幂等。全量测试:454 用例 / 451 通过 / 3 跳过 / 0 失败;CI 9/9 通过。
本地复现:
dotnet run --project src/ImeWlConverterCmd -- -i libimetxt -o libimebin -O out.dict in.txt # 与 libime_pinyindict 的产物对比 sha256,或直接跑 LibIMEPinyinTest兼容性
ZstdSharp.Port精确锁定0.8.8:字节一致依赖该移植版 zstd 的输出与上游 libzstd 相同,升级该包属于行为变更,需重跑上述测试并对比真实词库产物。