溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

怎么在Node.js中流式處理大JSON文件

發(fā)布時(shí)間:2021-06-22 17:28:56 來(lái)源:億速云 閱讀:237 作者:chen 欄目:web開(kāi)發(fā)

這篇文章主要講解了“怎么在Node.js中流式處理大JSON文件”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“怎么在Node.js中流式處理大JSON文件”吧!

場(chǎng)景描述

問(wèn)題一:假設(shè)現(xiàn)在有一個(gè)場(chǎng)景,有一個(gè)大的 JSON  文件,需要讀取每一條數(shù)據(jù)經(jīng)過(guò)處理之后輸出到一個(gè)文件或生成報(bào)表數(shù)據(jù),怎么能夠流式的每次讀取一條記錄?

[   {"id": 1},   {"id": 2},   ... ]

問(wèn)題二:同樣一個(gè)大的 JSON 文件,我只讀取其中的某一塊數(shù)據(jù),想只取 list 這個(gè)對(duì)象數(shù)組怎么辦?

{  "list": [],   "otherList": [] }

在 Node.js 中我們可以基于以下幾種方式讀取數(shù)據(jù),也是通常首先能夠想到的:

  • fs.readFile():這個(gè)是一次性讀取數(shù)據(jù)到內(nèi)存,數(shù)據(jù)量大了都占用到內(nèi)存也不是好辦法,很容易造成內(nèi)存溢出。

  • fs.createReadStream():創(chuàng)建一個(gè)可讀流,能解決避免大量數(shù)據(jù)占用內(nèi)存的問(wèn)題,這是一個(gè)系統(tǒng)提供的基礎(chǔ) API  讀取到的是一個(gè)個(gè)的數(shù)據(jù)塊,因?yàn)槲覀兊?JSON 對(duì)象是結(jié)構(gòu)化的,也不能直接解決上面提的兩個(gè)問(wèn)題。

  • 還有一個(gè) require() 也可以加載 JSON 文件,但是稍微熟悉點(diǎn) Node.js CommonJS 規(guī)范的應(yīng)該知道 require  加載之后是會(huì)緩存的,會(huì)一直占用在服務(wù)的內(nèi)存里。

了解下什么是 SAX

SAX 是 Simple API for XML 的簡(jiǎn)稱(chēng),目前沒(méi)有一個(gè)標(biāo)準(zhǔn)的 SAX 參考標(biāo)準(zhǔn),最早是在 Java  編程語(yǔ)言里被實(shí)現(xiàn)和流行開(kāi)的,以 Java 對(duì) SAX  的實(shí)現(xiàn)后來(lái)也被認(rèn)為是一種規(guī)范。其它語(yǔ)言的實(shí)現(xiàn)也是遵循著該規(guī)則,盡管每門(mén)語(yǔ)言實(shí)現(xiàn)都有區(qū)別,但是這里有一個(gè)重要的概念 “事件驅(qū)動(dòng)” 是相同的。

實(shí)現(xiàn)了 SAX 的解析器擁有事件驅(qū)動(dòng)那樣的 API,像 Stream 的方式來(lái)工作,邊讀取邊解析,用戶(hù)可以定義回調(diào)函數(shù)獲取數(shù)據(jù),無(wú)論 XML  內(nèi)容多大,內(nèi)存占用始終都會(huì)很小。

這對(duì)我們本節(jié)有什么幫助?我們讀取解析一個(gè)大 JSON 文件的時(shí)候,也不能把所有數(shù)據(jù)都加載到內(nèi)存里,我們也需要一個(gè)類(lèi)似 SAX  這樣的工具幫助我們實(shí)現(xiàn)。

基于 SAX 的流式 JSON 解析器

這是一個(gè)流式 JSON 解析器 https://github1s.com/creationix/jsonparse  周下載量在 600 多萬(wàn),但是這個(gè)源碼看起來(lái)很難梳理。如果是學(xué)習(xí),推薦一個(gè)基于 SAX 的更簡(jiǎn)單版本

https://gist.github.com/creationix/1821394 感興趣的可以看看。

JSON 是有自己的標(biāo)準(zhǔn)的,有規(guī)定的數(shù)據(jù)類(lèi)型、格式。這個(gè) JSON  解析器也是在解析到特定的格式或類(lèi)型后觸發(fā)相應(yīng)的事件,我們?cè)谑褂脮r(shí)也要注冊(cè)相應(yīng)的回調(diào)函數(shù)。

下面示例,創(chuàng)建一個(gè)可讀流對(duì)象,在流的 data 事件里注冊(cè) SaxParser 實(shí)例對(duì)象的 parse 方法,也就是將讀取到的原始數(shù)據(jù)(默認(rèn)是  Buffer 類(lèi)型)傳遞到 parse() 函數(shù)做解析,當(dāng)解析到數(shù)據(jù)之后觸發(fā)相應(yīng)事件。

對(duì)應(yīng)的 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 pparse = p.parse.bind(p); stream.on('data', parse);

怎么去解析一個(gè) JSON 文件的數(shù)據(jù)已經(jīng)解決了,但是如果直接這樣使用還是需要在做一些處理工作的。

JSONStream 處理大文件

這里推薦一個(gè) NPM 模塊 JSONStream,在它的實(shí)現(xiàn)中就是依賴(lài)的 jsonparse  這個(gè)模塊來(lái)解析原始的數(shù)據(jù),在這基礎(chǔ)之上做了一些處理,根據(jù)一些匹配模式返回用戶(hù)想要的數(shù)據(jù),簡(jiǎn)單易用。

下面我們用 JSONStream 解決上面提到的兩個(gè)問(wèn)題。

問(wèn)題一:

假設(shè)現(xiàn)在有一個(gè)場(chǎng)景,有一個(gè)大的 JSON  文件,需要讀取每一條數(shù)據(jù)經(jīng)過(guò)處理之后輸出到一個(gè)文件或生成報(bào)表數(shù)據(jù),怎么能夠流式的每次讀取一條記錄?

因?yàn)闇y(cè)試,所以我將 highWaterMark 這個(gè)值調(diào)整了下,現(xiàn)在我們的數(shù)據(jù)是下面這樣的。

[   { "id": 1 },   { "id": 2 } ]

重點(diǎn)是 JSONStream 的 parse 方法,我們傳入了一個(gè) '.',這個(gè) data 事件也是該模塊自己處理過(guò)的,每次會(huì)為我們返回一個(gè)對(duì)象:

  • 第一次返回 { 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); })()
問(wèn)題二:

同樣一個(gè)大的 JSON 文件,我只讀取其中的某一塊數(shù)據(jù),想只取 list 這個(gè)數(shù)組對(duì)象怎么辦?

解決第二個(gè)問(wèn)題,現(xiàn)在我們的 JSON 文件是下面這樣的。

{   "list": [     { "name": "1" },     { "name": "2" }   ],   "other": [     { "key": "val" }   ] }

與第一個(gè)解決方案不同的是改變了 parse('list.*') 方法,現(xiàn)在只會(huì)返回 list 數(shù)組,other 是不會(huì)返回的,其實(shí)在 list  讀取完成之后這個(gè)工作就結(jié)束了。

  • 第一次返回 { 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); })();
總結(jié)

當(dāng)我們遇到類(lèi)似的大文件需要處理時(shí),盡可能避免將所有的數(shù)據(jù)存放于內(nèi)存操作,應(yīng)用服務(wù)的內(nèi)存都是有限制的,這也不是最好的處理方式。

文中主要介紹如何流式處理類(lèi)似的大文件,更重要的是掌握編程中的一些思想,例如 SAX 一個(gè)核心點(diǎn)就是實(shí)現(xiàn)了 “事件驅(qū)動(dòng)” 的設(shè)計(jì)模式,同時(shí)結(jié)合 Stream  做到邊讀取邊解析。

處理問(wèn)題的方式是多樣的,還可以在生成 JSON 文件時(shí)做拆分,將一個(gè)大文件拆分為不同的小文件。

學(xué)會(huì)尋找答案,NPM 生態(tài)發(fā)展的還是不錯(cuò)的,基本上你能遇到的問(wèn)題大多已有一些解決方案了,例如本次問(wèn)題,不知道如何使用 Stream 來(lái)讀取一個(gè) JSON  文件時(shí),可以在 NPM 上搜索關(guān)鍵詞嘗試著找下。

怎么在Node.js中流式處理大JSON文件

感謝各位的閱讀,以上就是“怎么在Node.js中流式處理大JSON文件”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)怎么在Node.js中流式處理大JSON文件這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI