← 返回博客

2026年如何在不崩溃的情况下解析大型JSON文件

关于在JavaScript和Python中处理大型JSON文件(100MB+)的完整指南。学习流式传输、渐进解析和分块技术以避免内存错误。

Big JSON Team15 分钟阅读programming
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

15 分钟阅读

# 2026年如何在不崩溃的情况下解析大型JSON文件

介绍

您是否在尝试解析大型JSON文件时遇到过"FATAL ERROR: JavaScript heap out of memory"错误?您并不孤单。处理大型JSON文件(100MB+)是现代Web开发中最常见的挑战之一。

在这份综合指南中,您将学习经过验证的技术,以便在不耗尽内存或使应用程序崩溃的情况下高效解析大型JSON文件。

为什么JSON.parse()在大文件上失败

标准的JSON.parse()方法一次性将整个文件加载到内存中:

// ⚠️ 对大文件不好(>100MB)

const fs = require('fs');

const data = fs.readFileSync('huge-file.json', 'utf8');

const json = JSON.parse(data); // 消耗大量内存!

为什么会失败:
  • 将整个字符串存储在内存中(文件大小的2-3倍)
  • 创建完整的JavaScript对象(又是文件大小的2-5倍)
  • 在完成之前没有内存释放
  • 总计: 一个500MB的文件可能使用超过2GB的内存!

解决方案1: 使用JSONStream进行流式传输(Node.js)

处理大型JSON文件的最佳方法是流式传输 - 逐块处理数据。

实际示例

const fs = require('fs');

const JSONStream = require('JSONStream');

// 解析对象数组

const stream = fs.createReadStream('large-dataset.json', { encoding: 'utf8' });

const parser = JSONStream.parse(''); // 数组元素用''

let count = 0;

parser.on('data', (item) => {

// 一次处理一个项目

console.log(处理记录${++count}:, item.id);

// 您的逻辑在这里 - 插入数据库、转换等

if (item.price > 1000) {

saveToDatabase(item);

}

});

parser.on('end', () => {

console.log(成功处理了${count}条记录);

});

parser.on('error', (err) => {

console.error('解析错误:', err);

});

stream.pipe(parser);

解析嵌套的JSON路径

// 对于: { "users": [ {...}, {...} ] }

const parser = JSONStream.parse('users.');

// 对于: { "data": { "items": [...] } }

const parser = JSONStream.parse('data.items.');

// 对于: 深层结构或复杂条件

const parser = JSONStream.parse(['data', 'nested', { emitKey: true }]);

性能对比

| 方法 | 文件大小 | 内存使用 | 时间 |

|------|---------|---------|------|

| JSON.parse() | 500MB | ~2.1GB 💥 | 8秒 |

| JSONStream | 500MB | ~45MB ✅ | 12秒 |

| stream-json | 500MB | ~38MB ✅ | 10秒 |

基准测试: Node.js 20, Mac M1, 16GB RAM

解决方案2: stream-json(更快)

stream-json比JSONStream更快,并提供更多控制:
const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('huge-array.json'),

parser(),

streamArray()

]);

pipeline.on('data', ({ key, value }) => {

console.log(索引${key}:, value.name);

// 在这里处理

processRecord(value);

});

pipeline.on('end', () => console.log('完成!'));

用于JSON对象(非数组)

const { streamObject } = require('stream-json/streamers/StreamObject');

const pipeline = chain([

fs.createReadStream('huge-object.json'),

parser(),

streamObject()

]);

pipeline.on('data', ({ key, value }) => {

console.log(键: ${key}, value);

});

解决方案3: 分块读取(浏览器)

在浏览器中,使用FileReader逐步处理文件:

async function parseHugeJSON(file) {

const chunkSize = 1024 1024; // 1MB

let offset = 0;

let buffer = '';

let results = [];

while (offset < file.size) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

buffer += text;

// 尝试解析完整对象

const lines = buffer.split('\n');

buffer = lines.pop(); // 保留不完整的行

for (const line of lines) {

if (line.trim()) {

try {

const obj = JSON.parse(line);

results.push(obj);

// 批量处理以避免内存堆积

if (results.length >= 1000) {

await processBatch(results);

results = [];

}

} catch (e) {

console.warn('跳过无效行:', line.substring(0, 50));

}

}

}

offset += chunkSize;

// 报告进度

const progress = (offset / file.size 100).toFixed(1);

console.log(进度: ${progress}%);

}

// 处理最后一批

if (results.length > 0) {

await processBatch(results);

}

}

// 使用

document.getElementById('fileInput').addEventListener('change', async (e) => {

const file = e.target.files[0];

await parseHugeJSON(file);

});

解决方案4: Web Workers(非阻塞)

为防止UI冻结,将解析移至Web Worker:

// worker.js

self.onmessage = async function(e) {

const { file } = e.data;

const chunkSize = 1024 1024;

let processed = 0;

for (let offset = 0; offset < file.size; offset += chunkSize) {

const chunk = file.slice(offset, offset + chunkSize);

const text = await chunk.text();

// 处理块

const lines = text.split('\n').filter(l => l.trim());

const objects = lines.map(line => {

try {

return JSON.parse(line);

} catch {

return null;

}

}).filter(Boolean);

// 发送结果

self.postMessage({

type: 'progress',

data: objects,

percent: (offset / file.size 100)

});

processed += objects.length;

}

self.postMessage({ type: 'complete', total: processed });

};

// main.js

const worker = new Worker('worker.js');

worker.onmessage = function(e) {

if (e.data.type === 'progress') {

console.log(已处理: ${e.data.percent.toFixed(1)}%);

updateUI(e.data.data);

} else if (e.data.type === 'complete') {

console.log(完成! 总计: ${e.data.total});

}

};

// 开始处理

const fileInput = document.getElementById('upload');

fileInput.addEventListener('change', (e) => {

worker.postMessage({ file: e.target.files[0] });

});

解决方案5: API分页

如果从API获取JSON,使用分页:

async function fetchAllData(apiUrl) {

let allData = [];

let page = 1;

let hasMore = true;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// 立即处理页面

await processPage(data.items);

allData.push(...data.items);

hasMore = data.hasNextPage;

page++;

// 添加延迟以避免速率限制

await new Promise(resolve => setTimeout(resolve, 100));

}

return allData;

}

// 优化版本: 不存储所有内容

async function processAllData(apiUrl) {

let page = 1;

let hasMore = true;

let totalProcessed = 0;

while (hasMore) {

const response = await fetch(${apiUrl}?page=${page}&limit=100);

const data = await response.json();

// 不存储直接处理

for (const item of data.items) {

await processItem(item); // 保存到数据库、转换等

totalProcessed++;

}

hasMore = data.hasNextPage;

page++;

console.log(到目前为止已处理${totalProcessed}个项目...);

}

return totalProcessed;

}

解决方案6: 使用ijson的Python

对于服务器端处理,Python的ijson非常出色:

import ijson

def process_large_json(filename):

with open(filename, 'rb') as file:

# 解析对象数组

objects = ijson.items(file, 'item')

count = 0

for obj in objects:

# 处理每个元素

if obj.get('price', 0) > 1000:

save_to_database(obj)

count += 1

if count % 1000 == 0:

print(f"已处理{count}条记录...")

print(f"总计: {count}条记录")

# 用于嵌套结构

def process_nested(filename):

with open(filename, 'rb') as file:

# 仅提取特定键

for user_id in ijson.items(file, 'users.item.id'):

print(f"用户ID: {user_id}")

优化策略

1. 批处理而非单独处理

let batch = [];

const BATCH_SIZE = 1000;

parser.on('data', async (item) => {

batch.push(item);

if (batch.length >= BATCH_SIZE) {

await insertBatch(batch); // 一次批量插入

batch = [];

}

});

parser.on('end', async () => {

if (batch.length > 0) {

await insertBatch(batch); // 处理最后一批

}

});

2. 提前过滤数据

const { pick } = require('stream-json/filters/Pick');

const { streamArray } = require('stream-json/streamers/StreamArray');

const pipeline = chain([

fs.createReadStream('data.json'),

parser(),

pick({ filter: 'users' }), // 仅"users"属性

streamArray()

]);

3. 文件压缩

const zlib = require('zlib');

const pipeline = chain([

fs.createReadStream('data.json.gz'),

zlib.createGunzip(), // 动态解压缩

parser(),

streamArray()

]);

常见场景和解决方案

场景1: 从JSON导入数据库

const { chain } = require('stream-chain');

const { parser } = require('stream-json');

const { streamArray } = require('stream-json/streamers/StreamArray');

const { MongoClient } = require('mongodb');

async function importToMongo(filename, collectionName) {

const client = await MongoClient.connect('mongodb://localhost:27017');

const collection = client.db('mydb').collection(collectionName);

let batch = [];

const BATCH_SIZE = 1000;

const pipeline = chain([

fs.createReadStream(filename),

parser(),

streamArray()

]);

for await (const { value } of pipeline) {

batch.push(value);

if (batch.length >= BATCH_SIZE) {

await collection.insertMany(batch);

console.log(插入了${batch.length}个文档);

batch = [];

}

}

if (batch.length > 0) {

await collection.insertMany(batch);

}

await client.close();

}

场景2: 将JSON转换为CSV

const { Transform } = require('stream');

const csvWriter = require('csv-write-stream');

const jsonToCSV = new Transform({

objectMode: true,

transform(chunk, encoding, callback) {

// 将JSON对象转换为CSV行

callback(null, {

id: chunk.id,

name: chunk.name,

email: chunk.email

});

}

});

const writer = csvWriter();

writer.pipe(fs.createWriteStream('output.csv'));

pipeline

.pipe(jsonToCSV)

.pipe(writer);

场景3: 处理换行分隔的JSON(NDJSON)

const readline = require('readline');

async function processNDJSON(filename) {

const fileStream = fs.createReadStream(filename);

const rl = readline.createInterface({

input: fileStream,

crlfDelay: Infinity

});

let count = 0;

for await (const line of rl) {

if (line.trim()) {

const obj = JSON.parse(line);

await processObject(obj);

count++;

}

}

console.log(处理了${count}行);

}

故障排除

错误: JavaScript heap out of memory

解决方案:
# 在Node.js中增加内存限制

node --max-old-space-size=4096 script.js

但更好的方法: 使用流式传输!

错误: 分块读取时JSON无效

问题: 块可能会分割JSON对象。 解决方案: 使用行分隔的JSON(NDJSON):
// 首先将JSON转换为NDJSON

const items = require('./data.json'); // 一次性小量加载

const stream = fs.createWriteStream('data.ndjson');

items.forEach(item => {

stream.write(JSON.stringify(item) + '\n');

});

stream.end();

错误: 处理过程太慢

优化:
  • 使用stream-json而不是JSONStream(快2倍)
  • 增加批量大小(1000 → 5000)
  • 并行化数据库插入
  • 使用数据库连接池
  • 在批量插入前添加索引
  • 推荐工具

    用于JSON验证

    使用BigJSON.online快速验证和检查大型JSON文件而无需完全加载它们。支持流式传输和实时验证。

    • Node.js: stream-json(最快)、JSONStream(最简单)
    • Python: ijson(最好)
    • 浏览器: FileReader API + Web Workers

    结论

    快速建议:

    | 使用场景 | 最佳解决方案 | 内存使用 |

    |---------|------------|---------|

    | Node.js(大数组) | stream-json | 非常低 |

    | Node.js(简单结构) | JSONStream | 低 |

    | 浏览器(< 50MB) | 分块读取 | 中等 |

    | 浏览器(> 50MB) | Web Workers | 低 |

    | API下载 | 分页 + 流处理 | 非常低 |

    | Python | ijson | 非常低 |

    关键要点:
    • 始终使用流式传输处理> 50MB的文件
    • 批量处理用于数据库插入
    • 提前过滤以减少内存使用
    • 不要对大文件使用JSON.parse()
    • 不要一次性将所有内容加载到内存

    使用现代流式传输包,您可以轻松处理GB级的JSON文件而不会耗尽内存或崩溃。

    其他资源

    觉得这个指南有用吗?与正在与大型JSON文件作斗争的开发者同事分享!

    Share:

    相关文章

    Read in English