Node.js处理10GB/850MB大JSON文件卡顿报错解决方案咨询
大体积JSON订单文件处理方案
核心问题原因
你之前的两种实现都属于全量加载模式:
- 第一次用JSONStream时配置了
parse('*'),会将整个JSON结构完整解析后存入orders数组,等于还是把全部100万+订单对象加载到内存,自然会触发卡顿 - 第二次直接拼接整个文件流到字符串再调用
JSON.parse,850MB的文本长度超出了V8引擎的字符串最大长度限制,因此抛出RangeError: Invalid string length错误
正确实现方案
用流式JSON解析的正确姿势是只匹配orders数组下的单个订单节点,每解析完成一个订单就处理,处理完直接丢弃不保留全量数据,内存占用可以稳定在几十MB级别。
首先安装依赖:
npm install JSONStream event-stream
1. 统计订单总数量
仅计数不存储订单对象,内存占用极低:
const fs = require('fs'); const JSONStream = require('JSONStream'); const es = require('event-stream'); function countOrders(filePath) { return new Promise((resolve, reject) => { let orderCount = 0; fs.createReadStream(filePath, { encoding: 'utf8' }) // 仅匹配orders数组下的每个子元素,逐订单解析 .pipe(JSONStream.parse('orders.*')) .pipe(es.mapSync(() => { orderCount++; })) .on('end', () => resolve({ count: orderCount })) .on('error', (err) => reject(err)); }); }
2. 按订单ID查找对应订单
找到匹配项后直接终止文件读取,不需要遍历整个文件:
function findOrderById(filePath, targetOrderId) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filePath, { encoding: 'utf8' }); let matchedOrder = null; readStream .pipe(JSONStream.parse('orders.*')) .pipe(es.mapSync((order) => { if (order.id === targetOrderId) { matchedOrder = order; // 找到目标直接销毁流,停止后续文件读取 readStream.destroy(); } })) // 销毁流会触发close事件,结束后返回结果 .on('close', () => resolve(matchedOrder)) // 遍历完全部文件仍未找到也返回结果 .on('end', () => resolve(matchedOrder)) .on('error', (err) => reject(err)); }); }
3. 按起始ID和数量限制获取订单集合
取够指定数量后直接终止读取:
function getOrderList(filePath, startOrderId, limit) { return new Promise((resolve, reject) => { const readStream = fs.createReadStream(filePath, { encoding: 'utf8' }); const resultList = []; // 如果未传起始ID,直接从第一个订单开始取 let isStart = startOrderId == null; readStream .pipe(JSONStream.parse('orders.*')) .pipe(es.mapSync((order) => { if (isStart) { resultList.push(order); // 取够指定数量直接终止读取 if (resultList.length >= limit) readStream.destroy(); return; } // 匹配到起始ID后开始收集 if (order.id === startOrderId) { isStart = true; resultList.push(order); if (resultList.length >= limit) readStream.destroy(); } })) .on('close', () => resolve(resultList)) .on('end', () => resolve(resultList)) .on('error', (err) => reject(err)); }); }
优化建议
如果需要多次执行查询操作,建议首次遍历文件时构建ID与文件偏移量的索引表,后续查询可以直接跳转到对应位置读取,无需每次都遍历整个文件,查询效率可以提升百倍以上。
内容的提问来源于stack exchange,提问作者Deep Kakkar
相关产品推荐
相关产品推荐

