应使用 simdjson 的 ondemand API 进行 PB 级 JSON 流式解析,因其支持零拷贝与拉取式解析,避免 OOM;需禁用竞速模块、手动分块读取、跳过无关字段、检查 error_code 而非依赖异常,并注意 value 生命周期。

用 simdjson 做流式解析,不是用 rapidjson 或 nlohmann/json
普通 JSON 库会把整个文件加载进内存再 parse,PB 级文件直接 OOM。必须用支持「零拷贝 + 拉取式 API」的库。simdjson 是目前唯一在生产环境验证过 PB 级流式处理能力的 C++ 库(v3.0+ 支持 ondemand API)。rapidjson 的 ParseStream 只能省一点内存,仍需构造 DOM 树;nlohmann/json 根本不支持流式。
实操建议:
- 编译时启用
-DSIMDJSON_COMPETITION=OFF关闭冗余竞速模块,减小二进制体积 - 用
simdjson::ondemand::parser配合std::ifstream的rdbuf(),不要调parse_file()—— 后者仍会 mmap 整个文件 - 确保输入文件是 UTF-8 无 BOM,否则
simdjson会在首字节校验失败并报simdjson::error_code::UTF8_ERROR
只提取关键字段,跳过无关 object/array
PB 级 JSON 通常是日志或埋点数据,结构高度重复(如每个 item 是 {"ts":171..., "uid":"U123", "event":"click", ...}),不需要完整解析每个对象。用 ondemand::object::find_field_unordered() + skip() 跳过未用字段,比遍历所有 key/value 快 3–5 倍。
示例:只取 uid 和 event,忽略 props(可能嵌套很深):
for (auto tweet : parser.parse(input).get_array()) {
auto obj = tweet.get_object();
std::string_view uid = obj.find_field_unordered("uid").get_string();
std::string_view event = obj.find_field_unordered("event").get_string();
obj.find_field_unordered("props").skip(); // 直接跳过整个子树
process(uid, event);
}
注意:find_field_unordered 不保证顺序,但比 find_field 快;若字段名拼错,会抛 simdjson::NO_SUCH_FIELD 异常,务必捕获。
分块读取 + 内存映射要手动控制粒度
simdjson 默认一次处理 64KB(DOM_BUFFER_SIZE),对 PB 文件来说太小——频繁系统调用拖慢 I/O;太大又可能触发大页分配失败。实际应按存储介质调整:
- SSD:设为
1MB缓冲区(ondemand::parser parser{1024*1024}) - HDFS 或 NFS:降为
256KB,避免网络 buffer 拥塞 - 绝对不要用
std::ifstream::seekg()在解析中途跳转——ondemand解析器状态不可逆,seek 会导致INCORRECT_TYPE
如果文件是分块压缩(如 .json.gz),必须先用 zlib 或 libdeflate 流式解压到内存 buffer,再喂给 simdjson::padded_string —— 它不接受 gzip 流。
错误恢复不能靠 try/catch,得检查 error_code
流式解析中单条记录损坏(如某行 JSON 格式错误)很常见。用异常机制捕获会中断整个解析流,且 simdjson 的异常开销极大。正确做法是每步都检查返回的 simdjson::error_code:
auto doc = parser.iterate(input);
if (doc.error() == simdjson::SUCCESS) {
for (auto value : doc.get_array()) {
auto obj = value.get_object();
if (obj.error() != simdjson::SUCCESS) continue; // 跳过这行
auto uid = obj.find_field_unordered("uid");
if (uid.error() != simdjson::SUCCESS) continue;
// ...
}
}
容易被忽略的是:ondemand::value 类型的临时对象生命周期极短,一旦离开作用域就失效。若需跨函数传递字段值,必须用 std::string_view(仅当源 buffer 持有者还活着)或显式 std::string 拷贝。buffer 复用时尤其要注意这点。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











