Node.js如何逐行读取大型JSON文件(无需全量加载到内存)
Node.js 大体积JSON文件流式处理+多文件并发读取方案
处理GB级大JSON文件的核心思路是放弃全量加载到内存的模式,使用流式读取逐段/逐行解析,配合Promise的并发能力即可实现多文件同时处理,以下分别给出原生实现和第三方库可选方案。
原生实现方案(无需第三方依赖)
该方案适用于行存储格式的JSON文件(也叫JSON Lines,每行对应一个完整的JSON对象,日常日志、结构化导出文件大多是这种格式),基于Node.js内置的fs、readline模块实现,内存占用仅和单次读取的缓冲区、匹配结果数量有关,500MB文件运行时内存占用可控制在100MB以内。
单文件流式搜索实现
你提供的代码已经符合流式处理的核心逻辑,这里做了小优化,删除了不必要的输出流实例:
const fs = require('fs'); const readline = require('readline'); const searchStream = (path, text = '') => new Promise((resolve, reject) => { const inStream = fs.createReadStream(path, { encoding: 'utf8' }); const rl = readline.createInterface({ input: inStream, crlfDelay: Infinity // 兼容所有换行符格式 }); const result = []; let count = 0; const regEx = new RegExp(text, 'i'); rl.on('line', (line) => { if (!line) return; // 若需要匹配JSON属性,可在此处先执行 const row = JSON.parse(line) 再判断 if (line.search(regEx) >= 0) { count++; result.push(line); } }); rl.on('close', () => resolve({ path, result, count })); rl.on('error', (err) => reject({ path, error: err })); });
多文件并发调用
直接使用Promise.all即可实现并发,若文件数量超过20个,建议加并发池限制最大同时读取的数量,避免IO占满影响性能:
// 无并发限制版本,适合文件数量较少的场景 const batchSearchFiles = async (filePaths, searchText) => { const searchResults = await Promise.all( filePaths.map(filePath => searchStream(filePath, searchText)) ); return searchResults; }; // 带并发限制版本,适合文件数量较多的场景,默认最大并发数为3 const promisePool = async (tasks, poolSize = 3) => { const result = []; const executing = []; for (const task of tasks) { const p = Promise.resolve().then(() => task()); result.push(p); if (poolSize <= tasks.length) { const e = p.then(() => executing.splice(executing.indexOf(e), 1)); executing.push(e); if (executing.length >= poolSize) { await Promise.race(executing); } } } return Promise.all(result); }; const batchSearchWithLimit = async (filePaths, searchText, limit = 3) => { const tasks = filePaths.map(filePath => () => searchStream(filePath, searchText)); return promisePool(tasks, limit); };
特殊场景适配(单一大JSON对象)
如果你的文件是单个完整的JSON对象(不是每行一个JSON的格式),逐行读取会出现JSON截断无法解析的问题,推荐使用第三方库JSONStream做流式节点解析,仅提取你需要的JSON字段,不需要全量加载整个文件:
const JSONStream = require('JSONStream'); const fs = require('fs'); // 示例:提取JSON中rows数组下的所有元素,逐节点处理 const parseBigSingleJSON = (filePath, matchPath = 'rows.*') => new Promise((resolve, reject) => { const matchResults = []; fs.createReadStream(filePath) .pipe(JSONStream.parse(matchPath)) // 按JSON路径匹配需要提取的节点 .on('data', node => { // 按需处理匹配到的节点 matchResults.push(node); }) .on('end', () => resolve(matchResults)) .on('error', reject); });
注意事项
- 流式处理过程中不要把所有行/节点全量存入结果数组,仅保留符合要求的内容,否则内存占用依然会随文件体积上涨
- 可以给
createReadStream传递highWaterMark参数调整缓冲区大小,默认是64KB,大文件场景可调整为256KB或512KB提升读取效率 - 解析JSON时建议加
try catch包裹,避免个别行格式错误导致整个进程崩溃
内容的提问来源于stack exchange,提问作者Batuhan Isildak
相关产品推荐
相关产品推荐

