<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何在 Node.js 中流式處理大 JSON 文件

          共 5002字,需瀏覽 11分鐘

           ·

          2021-05-29 15:31


          今日文章由 “Node.js技術(shù)棧@五月君” 授權(quán)分享,正文從下面開始~


          解決一個問題不只要搜尋最終的答案,尋找答案的過程同樣也是重要的,善于思考與總結(jié)總歸是好的。

          本文介紹一個概念 SAX 的設(shè)計模式,這個概念雖然不是來源于 Node.js,但它解決問題的一些思想當(dāng)我們在使用 Node.js 或一些其它的編程語言中遇到類似問題時也會受到一些啟發(fā),本文后面會介紹如何流式處理一個大 JSON 文件,下面先給出了兩個問題,可以先思考下如果是你會怎么做?

          場景描述

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

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

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

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

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

          • fs.readFile():這個是一次性讀取數(shù)據(jù)到內(nèi)存,數(shù)據(jù)量大了都占用到內(nèi)存也不是好辦法,很容易造成內(nèi)存溢出。
          • fs.createReadStream():創(chuàng)建一個可讀流,能解決避免大量數(shù)據(jù)占用內(nèi)存的問題,這是一個系統(tǒng)提供的基礎(chǔ) API 讀取到的是一個個的數(shù)據(jù)塊,因為我們的 JSON 對象是結(jié)構(gòu)化的,也不能直接解決上面提的兩個問題。
          • 還有一個 require() 也可以加載 JSON 文件,但是稍微熟悉點 Node.js CommonJS 規(guī)范的應(yīng)該知道 require 加載之后是會緩存的,會一直占用在服務(wù)的內(nèi)存里。

          了解下什么是 SAX

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

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

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

          基于 SAX 的流式 JSON 解析器

          這是一個流式 JSON 解析器 https://github1s.com/creationix/jsonparse 周下載量在 600 多萬,但是這個源碼看起來很難梳理。如果是學(xué)習(xí),推薦一個基于 SAX 的更簡單版本 https://gist.github.com/creationix/1821394 感興趣的可以看看。

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

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

          對應(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 parse = p.parse.bind(p);
          stream.on('data', parse);

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

          JSONStream 處理大文件

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

          下面我們用 JSONStream 解決上面提到的兩個問題。

          問題一:

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

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

          [
            { "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 文件,我只讀取其中的某一塊數(shù)據(jù),想只取 list 這個數(shù)組對象怎么辦?

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

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

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

          • 第一次返回 { name: '1' }
          • 第二次返回 { name: '2' }
          (async () => {
            const readable = fs.createReadStream('./list.json', {
              encoding'utf8',
              highWaterMark10
            })
            const parser = JSONStream.parse('list.*');
            readable.pipe(parser);
            parser.on('data'console.log);
          })();

          總結(jié)

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

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

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

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

          最后

          如果覺得這篇文章還不錯
          點擊下面卡片關(guān)注我
          來個【分享、點贊、在看】三連支持一下吧

             “分享、點贊在看” 支持一波  

          瀏覽 72
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧洲老太bb视频网站 | 大香蕉伊人视频在线 | 在线国产视频福利 | 老肥骚逼 | 青青草无码视频免费播放 |