告别 JSON:我手写二进制格式省了 80% 空间
Packing Binary Is Fun
看到有人在 Twitter 上争论说存 JSON 不是 Real™ Developer 该做的事,我决定亲自下场验证。结果发现,二进制打包确实能大幅压缩数据。从简单的 ASCII 编码到设计自定义的 varint 变长整数,再到构建一套完整的二进制 schema 语言,我最终开发出了 jBin。这套方案不仅能像 protobuf 那样通过 schema 隐式定义类型,还能将 JSON 负载体积缩小 80%。与其让程序员手动处理繁琐的字节流,不如用声明式的 schema 语言让编译器自动搞定这一切。
与其让底层程序员手动处理繁琐的格式化和编码,不如直接编写一套完整的 schema 语言,让程序自动处理那些枯燥的格式细节。
HN 评论区
16- trashb
我觉得这个(二进制)格式缺了一些我认为必不可少的部分:
- 魔数头(magic header)
- 版本号
- CRC 或数据损坏校验
此外,我认为在这种情况下,写一个自定义的文本解析器比解析二进制格式更好。这有点像类 Unix 配置文件与 Windows 注册表(regedit)之争。我倾向于使用 JSON 之外的格式,但依然要是可读的文本。
即使文章底部给出的那个 JSON 示例,如果把字段名替换成单个字符并去掉空格,也能从 418 个字符压缩到 166 个。这种格式节省的空间几乎都来自于省略字段名以及采用位置相关的布局。你可以选择使用分隔符,或者安排一个字节来指示类型(+大小)。例如,下面这个字符串编码的示例数据效率几乎一样高(85 字节 vs 80 字节),但依然可读,并且支持 utf-8 解释:
123456789;LeroyJenkins;60;alliance;p,100,200,300;i,999,1,1;i,45,100,0;a,s,120;a,a,45;
你还可以通过允许重复条目、允许从定义好的默认值中推断数值,并且只发送增量(delta)来进一步优化,这取决于你期望的数据类型。
{ "itemId": 999, "quantity": 1, "isSoulbound": false }
i,999,1,0
可以变成:
default = { "itemId": 0, "quantity": 1, "isSoulbound": false }
{ "itemId": 999}
i,999
- ErikHuisman
我也想做个真正的开发者,所以我直接把 JSON 用 GZIP 压缩一下,让它变成二进制。
- kstenerud
Schema(模式)能帮你省空间,但代价是你失去了脱离 Schema 就能理解数据的能力。这正是 JSON 尽管效率低下却依然好用的地方。
当然,二进制格式也能做到类似的事情。我写了一个可以直接替换 JSON 的二进制格式,因为写一个比 simjson 和 yyjson 快两倍的二进制解析器很容易。重要的是,不要给这个替换方案添加任何会破坏往返兼容性(roundtrip compatibility)的额外功能。
写这个项目挺有趣的,对我个人也很有用:https://github.com/kstenerud/bonjson