在 Nodejs 中解析大型 JSON 文件

2022-08-30 04:32:35

我有一个文件,它以JSON形式存储了许多JavaScript对象,我需要读取该文件,创建每个对象,并对它们执行某些操作(在我的情况下将它们插入到数据库中)。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!");
});*/

请注意,我希望防止将整个文件读入内存。时间效率对我来说并不重要。是的,我可以尝试一次读取多个对象并一次插入所有对象,但这是一种性能调整 - 我需要一种保证不会导致内存过载的方法,无论文件中包含多少对象。

我可以选择使用或其他东西,只是请在您的答案中指定。谢谢!FormatAFormatB


答案 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

如果存在换行符,则从开头切出缓冲区到换行符,并将其传递给 。然后,它再次检查缓冲区(循环)中是否有另一个换行符。通过这种方式,我们可以处理当前块中读取的所有行。pumpprocesswhile

最后,每个输入行调用一次。如果存在,它将去除回车符(以避免行尾问题 - LF 与 CRLF),然后调用一个行。此时,您可以对对象执行任何需要执行的操作。processJSON.parse

请注意,它对它接受的输入是严格的;您必须用双引号引用标识符和字符串值。换句话说,将抛出错误;您必须使用 .JSON.parse{name:'thing1'}{"name":"thing1"}

因为一次内存中不会有超过一块数据,所以这将是非常有效的内存。它也将非常快。一项快速测试显示,我在 15 毫秒内处理了 10,000 行。


答案 2

就像我认为编写流式JSON解析器会很有趣一样,我也认为也许我应该进行快速搜索,看看是否有一个已经可用。

原来是有的。

  • JSONStream “streaming JSON.parse and stringify”

由于我刚刚找到它,我显然没有使用它,所以我不能评论它的质量,但我很想听听它是否有效。

它确实考虑了以下Javascript和_.isString

stream.pipe(JSONStream.parse('*'))
  .on('data', (d) => {
    console.log(typeof d);
    console.log("isString: " + _.isString(d))
  });

如果流是对象数组,这将记录传入的对象。因此,唯一被缓冲的是一次一个对象。