在Nodejs中parsing大的JSON文件
我有一个存储JSONforms的JavaScript对象的文件,我需要读取文件,创build每个对象,并与他们做一些事情(插入到我的情况下分贝)。 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)); });
但是,文件可能真的很大,我宁愿使用stream来完成这一点。 我看到一个stream的问题是,文件内容可以在任何时候分解成数据块,所以我怎么能在这样的对象上使用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!"); });*/
请注意,我希望阻止将整个文件读入内存。 时间效率对我无关紧要。 是的,我可以尝试一次读取多个对象,并一次插入所有对象,但这是一个性能调整 – 我需要一种保证不会导致内存过载的方式,无论文件中包含多less个对象。
我可以select使用FormatA
或者FormatB
或者其他的东西,只需要在你的答案中指定。 谢谢!
要逐行处理文件,只需要将文件的读取和作用于该input的代码分开。 你可以通过缓冲你的input来达到这个目的,直到你换行。 假设每行有一个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! } }
每次文件stream从文件系统接收数据时,都将其存储在缓冲区中,然后调用pump
。
如果缓冲区中没有换行符,则pump
只是返回而不做任何事情。 更多的数据(和潜在的换行符)将在下次stream获取数据时被添加到缓冲区,然后我们将有一个完整的对象。
如果有一个换行符,将从开始到换行的缓冲区从切片中抽出并交给process
。 然后再检查缓冲区是否有另一个换行符( while
循环)。 这样,我们可以处理当前块中读取的所有行。
最后,每个input行调用一次process
。 如果存在的话,它删除回车符(以避免线结束的问题 – LF与CRLF),然后调用JSON.parse
一行。 在这一点上,你可以做任何你需要的东西与你的对象。
请注意, JSON.parse
是严格的接受作为input; 您必须用双引号引用您的标识符和string值。 换句话说, {name:'thing1'}
会抛出一个错误; 你必须使用{"name":"thing1"}
。
因为一次只能存储大量的数据,这将是非常有效的内存。 这也将是非常快的。 一个快速testing显示我在15ms以内处理了10,000行。
就像我在想写一个stream式JSONparsing器会很有趣一样,我也认为也许我应该快速search一下,看看是否已经有一个。
原来是有的。
- JSONStream “streamJSON.parse和stringify”
既然我刚刚find了,我显然没有使用它,所以我不能评论它的质量,但我会有兴趣听到它是否有效。
它的工作考虑以下CoffeeScript:
stream.pipe(JSONStream.parse('*')) .on 'data', (d) -> console.log typeof d console.log "isString: #{_.isString d}"
如果stream是一个对象数组,这将在对象进入时进行logging。 因此,唯一被缓冲的是一次一个对象。
截至2014年10月 ,你可以做如下的事情(使用JSONStream) – https://www.npmjs.org/package/JSONStream
var fs = require('fs'), JSONStream = require('JSONStream'), var getStream() = function () { var jsonData = 'myData.json', stream = fs.createReadStream(jsonData, {encoding: 'utf8'}), parser = JSONStream.parse('*'); return stream.pipe(parser); } getStream().pipe(MyTransformToDoWhateverProcessingAsNeeded).on('error', function (err){ // handle any errors });
为了演示一个工作的例子:
npm install JSONStream event-stream
data.json:
{ "greeting": "hello world" }
hello.js:
var fs = require('fs'), JSONStream = require('JSONStream'), es = require('event-stream'); var getStream = function () { var jsonData = 'data.json', stream = fs.createReadStream(jsonData, {encoding: 'utf8'}), parser = JSONStream.parse('*'); return stream.pipe(parser); }; getStream() .pipe(es.mapSync(function (data) { console.log(data); })); $ node hello.js // hello world
我意识到,如果可能的话,您要避免将整个JSON文件读入内存,但如果您有可用的内存,性能方面可能不是一个坏主意。 在json文件上使用node.js的require()可以非常快地将数据加载到内存中。
我运行了两个testing,以查看从81MB geojson文件的每个function打印出来的性能。
在第一个testing中,我使用var data = require('./geo.json')
将整个geojson文件读入内存。 这花了3330毫秒,然后从每个function打印出一个属性耗时804毫秒,总计4134毫秒。 但是,似乎node.js使用了411MB的内存。
在第二个testing中,我使用@ arcseldon的JSONStream +事件stream的答案。 我修改了JSONPath查询来select我所需要的。 这次内存永远不会超过82MB,但是现在整个事情花了70秒才完成!
我有类似的要求,我需要读取js节点中的大型json文件,并处理数据块,并调用api并保存在mongodb中。 inputFile.json就像:
{ "customers":[ { /*customer data*/}, { /*customer data*/}, { /*customer data*/}.... ] }
现在我使用JsonStream和EventStream来实现这个同步。
var JSONStream = require('JSONStream'); var es = require('event-stream'); fileStream = fs.createReadStream(filePath, {encoding: 'utf8'}); fileStream.pipe(JSONStream.parse('customers.*')).pipe(es.through(function (data) { console.log('printing one customer object read from file ::'); console.log(data); this.pause(); processOneCustomer(data, this); return data; },function end () { console.log('stream reading ended'); this.emit('end'); }); function processOneCustomer(data,es){ DataModel.save(function(err,dataModel){ es.resume(); }); }
我使用split npm模块解决了这个问题。 将stream分为两部分,它将“ 分解一个stream并重新组装,以便每一行都是一个块 ”。
示例代码:
var fs = require('fs') , split = require('split') ; var stream = fs.createReadStream(filePath, {flags: 'r', encoding: 'utf-8'}); var lineStream = stream.pipe(split()); linestream.on('data', function(chunk) { var json = JSON.parse(chunk); // ... });
我想你需要使用一个数据库。 在这种情况下,MongoDB是个不错的select,因为它是JSON兼容的。
更新 :您可以使用mongoimport工具将JSON数据导入到MongoDB中。
mongoimport --collection collection --file collection.json
如果您可以控制input文件,并且它是一个对象数组,则可以更轻松地解决这个问题。 安排在一行中输出每个logging的文件,如下所示:
[ {"key": value}, {"key": value}, ...
这仍然是有效的JSON。
然后,使用node.js readline模块一次处理它们一行。
var fs = require("fs"); var lineReader = require('readline').createInterface({ input: fs.createReadStream("input.txt") }); lineReader.on('line', function (line) { line = line.trim(); if (line.charAt(line.length-1) === ',') { line = line.substr(0, line.length-1); } if (line.charAt(0) === '{') { processRecord(JSON.parse(line)); } }); function processRecord(record) { // Process the records one at a time here! }