真正可行的流式解析方案只有换库(如rapidjson的sax模式)或手写状态机;rapidjson reader配合分块读取与自定义handler可实现内存恒定的真流式解析。

用 jsoncpp 的 Json::CharReader 做流式解析行不通
因为 Json::CharReader 本质仍是读完整字符串再解析,底层依赖 std::string 缓存,遇到 GB 级 JSON 直接 OOM。它不是 SAX,只是“伪流式”——你传进去的 const char* 还是得提前全加载好。
真正可行的路径只有两条:换库,或手写状态机。生产环境几乎都选前者。
-
jsoncpp、rapidjson的 DOM 模式(如Document::Parse)全量加载,禁用 -
rapidjson的 SAX 模式(Reader+Handler)是目前 C++ 里最稳的流式方案 -
nlohmann/json不支持 SAX,只提供json_sax_dom_callback_parser这种半吊子回调,无法真正流式处理超大文件
用 rapidjson 的 Reader 实现真 SAX 解析
核心是继承 BaseReaderHandler,重写 StartObject()、Key()、String() 等钩子函数,在数据到达时立刻消费,不保留中间结构。
关键实操点:
立即学习“C++免费学习笔记(深入)”;
- 别用
FILE*直接喂给Reader;先用fread分块读取到固定大小缓冲区(如 64KB),再调用Reader::Parse,并检查返回值是否为kParseErrorNone - 在
String()回调里拿到的是const char*+length,不是 null-terminated 字符串;若需转std::string,必须显式构造:std::string(s, length) - 嵌套层级深时,靠
depth_成员变量(需自己维护)判断当前路径;rapidjson不自动维护上下文,容易把数组里的字段错当成根对象字段
// 示例:只提取所有 "url" 字段值
class UrlHandler : public rapidjson::BaseReaderHandler<rapidjson::UTF8<>, rapidjson::UTF8<>> {
int depth_ = 0;
bool in_url_field_ = false;
public:
bool Key(const char* str, rapidjson::SizeType len, bool copy) override {
if (len == 3 && memcmp(str, "url", 3) == 0 && depth_ == 1) {
in_url_field_ = true;
}
return true;
}
bool String(const char* str, rapidjson::SizeType len, bool copy) override {
if (in_url_field_) {
printf("found url: %.*s\n", (int)len, str);
in_url_field_ = false;
}
return true;
}
// ... 其他必需重载函数(StartObject/EndObject 等)
};
分块读取时最容易崩的三个地方
流式 ≠ 随便切。JSON 是结构化文本,跨字符、跨转义、跨 UTF-8 多字节边界切,rapidjson 会直接报 kParseErrorInvalidUTF8 或静默跳过数据。
- 缓冲区末尾不能停在 UTF-8 多字节字符中间:读完一块后,检查最后一个字节是否属于
0xC0–0xF7(多字节起始),若是,回退到上一个合法字符边界再切 - 不能在字符串内部断开:如果上一块结尾是
"http://exa(引号未闭),下一块开头是mple.com",rapidjson会认为这是两个独立字符串,丢失语义 - 换行和空白不影响解析,但块内必须保证 JSON 结构完整——也就是说,块边界只能出现在顶层对象/数组的逗号之后、或键值对之间的空白处;实际做法是:每次读取后,用简易状态机扫描缓冲区末尾,找到最近的合法断点(如匹配的
}或]后的逗号或换行)
为什么不用 simdjson?它不是更快吗?
快,但不解决流式问题。simdjson 的 ondemand API 仍要求一次性映射整个文件(mmap),且对 >2GB 文件在 32 位系统或某些容器环境会失败;它优化的是单次解析速度,不是内存驻留量。
如果你的场景是“边下载边解析”,或者文件来自管道(stdin)、网络 socket,simdjson 就完全不可用——它不接受增量输入流。
真正要流式,就得接受“解析速度稍慢但内存恒定”的 trade-off。这时候 rapidjson::Reader 是目前最成熟、文档最清晰、错误提示最准的选择。
注意:所有回调函数里禁止做耗时操作(比如写磁盘、发 HTTP 请求),否则会卡住解析器;数据要攒一批再批量处理,别在 String() 里逐条 insert 到数据库。










