1. ホーム
  2. javascript

[解決済み] Nodejsで大きなJSONファイルをパースする

2022-07-22 20:04:57

質問

私は多くのJavaScriptオブジェクトをJSON形式で保存したファイルを持っており、ファイルを読み、各オブジェクトを作成し、それらを使って何かをする必要があります(私の場合、それらをデータベースに挿入する)。JavaScript オブジェクトは、次のようなフォーマットで表されます。

フォーマットA。

[{name: 'thing1'},
....
{name: 'thing999999999'}]

または フォーマットBです。

{name: 'thing1'}         // <== My choice.
...
{name: 'thing999999999'}

なお ... は多くのJSONオブジェクトを示しています。私はファイル全体をメモリに読み込むことができることを承知しており、その上で JSON.parse() のようにします。

fs.readFile(filePath, 'utf-8', function (err, fileContents) {
  if (err) throw err;
  console.log(JSON.parse(fileContents));
});

しかし、ファイルが非常に大きくなる可能性があるので、これを達成するためにストリームを使用することを希望します。ストリームの問題点は、ファイルのコンテンツが任意の時点でデータチャンクに分割される可能性があることです。 JSON.parse() をそのようなオブジェクトに使用できますか?

理想的には、各オブジェクトは個別のデータチャンクとして読み込まれますが、私は、以下のような をどのように行うか .

var importStream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
importStream.on('data', function(chunk) {

    var pleaseBeAJSObject = JSON.parse(chunk);           
    // insert pleaseBeAJSObject in a database
});
importStream.on('end', function(item) {
   console.log("Woot, imported objects into the database!");
});*/

なお、私はファイル全体をメモリに読み込まないようにしたいと考えています。時間効率は私にとって重要ではありません。しかし、それはパフォーマンスの微調整であり、ファイルに含まれるオブジェクトの数に関係なく、メモリ オーバーロードを引き起こさないことが保証されている方法が必要です。

私は FormatA または FormatB または何か他のものかもしれません。ありがとうございます。

どのように解決するのですか?

ファイルを行ごとに処理するには、単にファイルの読み込みとその入力に対応するコードを切り離す必要があります。 これは、改行に当たるまで入力をバッファリングすることで実現できます。 1 行に 1 つの JSON オブジェクトがあると仮定します (基本的に、フォーマット B)。

var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'});
var buf = '';

stream.on('data', function(d) {
    buf += d.toString(); // when data is read, stash it in a string buffer
    pump(); // then process the buffer
});

function pump() {
    var pos;

    while ((pos = buf.indexOf('\n')) >= 0) { // keep going while there's a newline somewhere in the buffer
        if (pos == 0) { // if there's more than one newline in a row, the buffer will now start with a newline
            buf = buf.slice(1); // discard it
            continue; // so that the next iteration will start with data
        }
        processLine(buf.slice(0,pos)); // hand off the line
        buf = buf.slice(pos+1); // and slice the processed data off the buffer
    }
}

function processLine(line) { // here's where we do something with a line

    if (line[line.length-1] == '\r') line=line.substr(0,line.length-1); // discard CR (0x0D)

    if (line.length > 0) { // ignore empty lines
        var obj = JSON.parse(line); // parse the JSON
        console.log(obj); // do something with the data here!
    }
}

ファイルストリームがファイルシステムからデータを受け取るたびに、バッファに格納され、その後 pump が呼び出されます。

バッファに改行がない場合 pump は何もせずに単に返します。 ストリームが次にデータを取得するときに、より多くのデータ(と潜在的に改行)がバッファに追加され、その後、完全なオブジェクトを取得することになります。

改行があった場合 pump はバッファの先頭から改行までをスライスし、それを process . そして、バッファの中に別の改行があるかどうかを再びチェックします ( while ループ)。 このようにして、現在のチャンクで読み込まれたすべての行を処理することができます。

最後に process は入力行ごとに一度だけ呼ばれます。 もし存在すれば、キャリッジリターン文字を取り除き (行末の問題 - LF と CRLF を回避するため)、それから JSON.parse を一行呼び出す。 この時点で、オブジェクトに対して必要なことを何でも行うことができます。

注意してほしいのは JSON.parse は入力として受け付けるものに厳しいので、識別子と文字列値を引用符で囲む必要があります。 を二重引用符で囲む必要があります。 . 言い換えると {name:'thing1'} はエラーを投げるので、必ず {"name":"thing1"} .

一度にメモリ上に存在するのは1つのデータチャンクだけなので、メモリ効率は非常に高くなります。 また、非常に高速になります。 簡単なテストでは、10,000 行を 15ms 未満で処理しました。