在 Node.js 中处理大 JSON 文件
在 Node.js 中处理大 JSON 文件
场景描述
问题一:
假设现在有一个场景,有一个大的 JSON 文件,需要读取每一条数据经过处理之后输出到一个文件或生成报表数据,怎么能够流式的每次读取一条记录?
[
{"id": 1},
{"id": 2},
...
]
问题二:
同样一个大的 JSON 文件,我只读取其中的某一块数据,想只取 list 这个对象数组怎么办?
{
"list": [],
"otherList": []
}
在 Node.js 中我们可以基于以下几种方式读取数据,也是通常首先能够想到的:
fs.readFile():这个是一次性读取数据到内存,数据量大了都占用到内存也不是好办法,很容易造成内存溢出。fs.createReadStream():创建一个可读流,能解决避免大量数据占用内存的问题,这是一个系统提供的基础API读取到的是一个个的数据块,因为我们的JSON对象是结构化的,也不能直接解决上面提的两个问题。- 还有一个
require()也可以加载JSON文件,但是稍微熟悉点Node.js CommonJS规范的应该知道require加载之后是会缓存的,会一直占用在服务的内存里。
什么是 SAX
SAX是 Simple API for XML 的简称,目前没有一个标准的SAX 参考标准,最早是在 Java 编程语言里被实现和流行开的,以Java对 SAX的实现后来也被认为是一种规范。其它语言的实现也是遵循着该规则,尽管每门语言实现都有区别,但是这里有一个重要的概念 “事件驱动” 是相同的。
实现了SAX的解析器拥有事件驱动那样的 API,像 Stream 的方式来工作,边读取边解析,用户可以定义回调函数获取数据,无论 XML 内容多大,内存占用始终都会很小。
这对我们本节有什么帮助?我们读取解析一个大 JSON文件的时候,也不能把所有数据都加载到内存里,我们也需要一个类似SAX 这样的工具帮助我们实现。
基于 SAX 的流式 JSON 解析器
这是一个流式 JSON 解析器 https://github1s.com/creationix/jsonparse 周下载量在 600 多万。
JSON 是有自己的标准的,有规定的数据类型、格式。这个 JSON 解析器也是在解析到特定的格式或类型后触发相应的事件,我们在使用时也要注册相应的回调函数。
下面示例,创建一个可读流对象,在流的 data事件里注册 SaxParser实例对象的parse 方法,也就是将读取到的原始数据(默认是Buffer 类型)传递到 parse()函数做解析,当解析到数据之后触发相应事件。
对应的 Node.js代码如下:
const SaxParser = require('./jsonparse').SaxParser;
const p = new SaxParser({
onNull: function () { console.log("onNull") },
onBoolean: function (value) { console.log("onBoolean", value) },
onNumber: function (value) { console.log("onNumber", value) },
onString: function (value) { console.log("onString", value) },
onStartObject: function () { console.log("onStartObject") },
onColon: function () { console.log("onColon") },
onComma: function () { console.log("onComma") },
onEndObject: function () { console.log("onEndObject") },
onStartArray: function () { console.log("onEndObject") },
onEndArray: function () { console.log("onEndArray") }
});
const stream = require('fs').createReadStream("./example.json");
const parse = p.parse.bind(p);
stream.on('data', parse);
怎么去解析一个JSON 文件的数据已经解决了,但是如果直接这样使用还是需要在做一些处理工作的。
JSONStream 处理大文件
这里推荐一个 NPM 模块 JSONStream,在它的实现中就是依赖的 jsonparse 这个模块来解析原始的数据,在这基础之上做了一些处理,根据一些匹配模式返回用户想要的数据,简单易用。
下面我们用JSONStream解决上面提到的两个问题。
问题一:
假设现在有一个场景,有一个大的 JSON 文件,需要读取每一条数据经过处理之后输出到一个文件或生成报表数据,怎么能够流式的每次读取一条记录?
因为测试,所以我将 highWaterMark 这个值调整了下,现在我们的数据是下面这样的。
[
{ "id": 1 },
{ "id": 2 }
]
重点是 JSONStream 的 parse 方法,我们传入了一个 '.',这个 data事件也是该模块自己处理过的,每次会为我们返回一个对象:
- 第一次返回
{ id: 1 } - 第二次返回
{ id: 2 }
const fs = require('fs');
const JSONStream = require('JSONStream');
(async () => {
const readable = fs.createReadStream('./list.json', {
encoding: 'utf8',
highWaterMark: 10
})
const parser = JSONStream.parse('.');
readable.pipe(parser);
parser.on('data', console.log);
})()
问题二:
同样一个大的 JSON 文件,我只读取其中的某一块数据,想只取 list 这个数组对象怎么办?
解决第二个问题,现在我们的 JSON 文件是下面这样的。
{
"list": [
{ "name": "1" },
{ "name": "2" }
],
"other": [
{ "key": "val" }
]
}
与第一个解决方案不同的是改变了 parse('list.*') 方法,现在只会返回 list 数组,other 是不会返回的,其实在 list 读取完成之后这个工作就结束了。
- 第一次返回
{ name: '1' } - 第二次返回
{ name: '2' }
(async () => {
const readable = fs.createReadStream('./list.json', {
encoding: 'utf8',
highWaterMark: 10
})
const parser = JSONStream.parse('list.*');
readable.pipe(parser);
parser.on('data', console.log);
})();
总结
当我们遇到类似的大文件需要处理时,尽可能避免将所有的数据存放于内存操作,应用服务的内存都是有限制的,这也不是最好的处理方式。
文中主要介绍如何流式处理类似的大文件,更重要的是掌握编程中的一些思想,例如SAX 一个核心点就是实现了事件驱动 的设计模式,同时结合 Stream做到边读取边解析。
处理问题的方式是多样的,还可以在生成 JSON文件时做拆分,将一个大文件拆分为不同的小文件。
在 Node.js 中处理大 JSON 文件的更多相关文章
- node.js 中的package.json文件怎么创建?
最近在用webstorm和nodejs做一些东西,老是各种混乱,今天上午创建一个新的项目,结果发现,npm init之后,并没有出现package.json,并没有太明确他的功能的小姑娘表示十分的惊慌 ...
- node.js 中的 fs (文件)模块
记录 fs 模块的方法及使用 1. fs.stat 获取文件大小,创建时间等信息 // 引入 fs 模块 const fs = require('fs'); fs.stat('01.fs.js', ( ...
- node.js中 express + multer 处理文件上传
multer中间件,可以很方便的结合express处理用户表单上传的文件. 一.安装multer npm install multer 二.处理单个文件上传 const express = requi ...
- node.js中process进程的概念和child_process子进程模块的使用
进程,你可以把它理解成一个正在运行的程序.node.js中每个应用程序都是进程类的实例对象. node.js中有一个 process 全局对象,通过它我们可以获取,运行该程序的用户,环境变量等信息. ...
- Node.js核心模块API之文件操作
参考:https://www.runoob.com/nodejs/nodejs-fs.html 异步I/O 1,文件操作 2,网络操作 在浏览器中也存在异步操作 1,定时任务 2,事件处理 3,Aja ...
- 初学Node(二)package.json文件
package.json简介 package.json在Node项目中用于描述项目的一些基本信息,以及依赖的配置,一般每一个Node项目的根目录下都有一个package.json文件. 在项目的根目录 ...
- node.js使用express框架进行文件上传
关于node.js使用express框架进行文件上传,主要来自于最近对Settings-Sync插件做的研究.目前的研究算是取得的比较好的进展.Settings-Sync中通过快捷键上传文件,其实主要 ...
- node.js中stream流中可读流和可写流的使用
node.js中的流 stream 是处理流式数据的抽象接口.node.js 提供了很多流对象,像http中的request和response,和 process.stdout 都是流的实例. 流可以 ...
- Node.js中环境变量process.env详解
Node.js中环境变量process.env详解process | Node.js API 文档http://nodejs.cn/api/process.html官方解释:process 对象是一个 ...
随机推荐
- javascript 字符串 数字反转 字母大小写互换
// 符串abcd123ABCD456 怎么转换为 ABCD321abcd654 // 数字要倒序 小写转大写, 大写转小写 Array.prototype.reverse = function() ...
- PolarDB PostgreSQL logindex 设计
背景介绍 PolarDB采用了共享存储一写多读架构,读写节点RW和多个只读节点RO共享同一份存储,读写节点可以读写共享存储中的数据:只读节点仅能各自通过回放日志,从共享存储中读取数据,而不能写入,只读 ...
- P7405-[JOI 2021 Final]雪玉【二分】
正题 题目链接:https://www.luogu.com.cn/problem/P7405 题目大意 \(n\)个点在坐标轴上,\(q\)次每次所有点向一个方向移动若干步,每个点的权值是它第一次覆盖 ...
- 经典软件测试面试题目:Android 和 ios 测试区别?这样回答:稳!
Android 和 ios 测试区别? App 测试中 ios 和 Android 有哪些区别呢?1.Android 长按 home 键呼出应用列表和切换应用,然后右滑则终止应用:2.多分辨率测试, ...
- Serverless 在 SaaS 领域的最佳实践
作者 | 计缘 来源 | Serverless 公众号 随着互联网人口红利逐渐减弱,基于流量的增长已经放缓,互联网行业迫切需要找到一片足以承载自身持续增长的新蓝海,产业互联网正是这一宏大背景下的新趋势 ...
- 洛谷T31018 经典题丶改(LCT+离线)
真的是一个大好题啊! QWQ首先我们考虑这种问题,如果直接在线做,估计应该是做不了,那我们是不是可以直接考虑离线. 将所有询问都按照\(r\)来排序. 然后依次加入每条边,计算\(a[i]<=n ...
- HTML5背景知识
目录 HTML5背景知识 HTML的历史 JavaScript出场 浏览器战争的结束 插件称雄 语义HTML浮出水面 发展态势:HTML标准滞后于其使用 HTML5简介 新标准 引入原生多媒体支持 引 ...
- GIS应用|快速开发REST数据服务
随着计算机的快速发展,GIS已经在各大领域得到应用,和我们的生活息息相关, 但是基于GIS几大厂商搭建服务,都会有一定的门槛,尤其是需要server,成本高,难度大,这里介绍一种在线GIS云平台,帮你 ...
- 这部分布式事务开山之作,凭啥第一天预售就拿下当当新书榜No.1?
大家好,我是冰河~~ 今天,咱们就暂时不聊[精通高并发系列]了,今天插播一下分布式事务,为啥?因为冰河联合猫大人共同创作的分布式事务领域的开山之作--<深入理解分布式事务:原理与实战>一书 ...
- 【UE4 设计模式】单例模式 Singleton Pattern
概述 描述 保证一个类只有一个实例 提供一个访问该实例的全局节点,可以视为一个全局变量 仅在首次请求单例对象时对其进行初始化. 套路 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符. ...