应采用流式解析而非全量加载:对ndjson用fgets()逐行处理,对单一大json对象用jsonstreamingparser库的sax模式回调处理,写入时用fopen+fwrite分块输出并临时文件保障原子性。

PHP读大JSON文件时内存爆掉怎么办
直接 json_decode(file_get_contents($path)) 会把整个文件一次性载入内存,100MB JSON轻松吃光512MB内存限制,报 Fatal error: Allowed memory size exhausted。这不是JSON格式问题,是加载方式错了。
核心思路:不全量加载,改用流式解析或分块处理。
- 优先用
json_decode()配合stream_get_line()或fgets()做行级JSON(如NDJSON/JSONL格式) - 纯单个大JSON对象(比如一个超长数组)必须用
JsonStreamingParser类库,它基于SAX模式逐节点解析,内存占用稳定在几MB内 - 别自己写正则或字符串截取来“模拟解析”——JSON嵌套和转义会让这种方案在第3层就崩溃
用JsonStreamingParser解析超大JSON数组的实际写法
这个类库不依赖扩展,纯PHP实现,适合线上环境。关键不是“怎么装”,而是“怎么写回调逻辑”。
常见错误是把所有数据攒进一个数组再处理,结果又回到内存爆炸的老路。
立即学习“PHP免费学习笔记(深入)”;
- 安装:
composer require sahilgupta/json-streaming-parser - 只在
onValue()回调里做原子操作:比如单条插入数据库、写入日志文件、发到消息队列 - 如果原始JSON是
{"data": [...]}这种带外层结构的,需在onKey()中识别"data"键,之后才开启数组项捕获 - 注意:它不支持重复键名覆盖,遇到同名字段会按顺序触发多次
onValue(),业务逻辑要能接受流式到达
use JsonStreamingParser\Parser;
use JsonStreamingParser\Stream\InputStream;
<p>$stream = new InputStream(fopen('big.json', 'r'));
$parser = new Parser($stream, new class() extends \JsonStreamingParser\Listener {
public function onValue($value) {
if (is_array($value) && isset($value['id'])) {
// 每次只处理一条,不累积
$this->processItem($value);
}
}
private function processItem($item) { /<em> 插入DB或写文件 </em>/ }
});写超大JSON文件时避免磁盘占满或中断丢失
file_put_contents($path, json_encode($huge_array)) 会先在内存拼出完整字符串,再写磁盘——两头风险:内存炸、进程被kill导致文件写半截。
正确做法是边生成边写,尤其当数据来自数据库游标或API分页时。
- 用
fopen($path, 'w')+fwrite()手动拼接,开头写"[",每条记录后加",",最后补"]" - 每写1000条调用一次
fflush(),防止缓冲区堆积和意外中断丢数据 - 不要用
JSON_PRETTY_PRINT—— 格式化会让体积膨胀30%以上,且严重拖慢写入速度 - 如果必须保证原子性(比如不能出现半截文件),先写到
$path . '.tmp',写完再rename()
PHP版本和JSON扩展对大文件的影响
PHP 7.4+ 的 json_decode() 在处理大整数时默认转成浮点,可能丢精度;而 ext-json 编译时若没开 --enable-json,连基础函数都没有——这些不会报错,但数据会静默出错。
- 检查是否启用:
var_dump(function_exists('json_decode')); - 大数字安全解析必须加
JSON_BIGINT_AS_STRING标志,否则9223372036854775807可能变成9.2233720368548E+18 - PHP 8.1+ 支持
JSON_THROW_ON_ERROR,比手动检查json_last_error()更可靠,推荐强制开启 - 别信“升级PHP就能自动优化大JSON”——底层还是全量解析,该爆内存照样爆
实际最难的不是选哪个方案,是判断原始JSON结构到底适配哪种解析路径:是单对象、数组、流式NDJSON,还是混着来的。看不清结构就硬上 json_decode(),后面八成要重写。











