解析Nodejs中的大型JSON文件
我有一个文件,它以JSON形式存储了许多JavaScript对象,我需要读取文件,创建每个对象,并对它们进行操作(在我的情况下将它们插入到数据库中) 。的JavaScript对象可被表示的格式:解析Nodejs中的大型JSON文件
格式答:
[{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
或其他什么东西,请在您的答案中指定。谢谢!
要逐行处理文件,只需将文件读取和作用于该输入的代码分开。你可以通过缓冲你的输入来达到这个目的,直到你输入一个换行符。假设我们有每行一个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
。如果存在,它将去掉回车符(以避免线结尾处的问题与LFLF相比较),然后调用JSON.parse
作为一条线。在这一点上,你可以做任何你需要的东西与你的对象。
请注意,JSON.parse
严格接受什么作为输入;您必须用双引号引用您的标识符和字符串值。换句话说,{name:'thing1'}
会抛出一个错误;您必须使用{"name":"thing1"}
。
因为一次只有一块数据永远在内存中,这将是非常有效的内存。它也会非常快。一个快速测试表明我在15ms以内处理了10,000行。
我认为你需要使用一个数据库。在这种情况下,MongoDB是个不错的选择,因为它兼容JSON。
更新: 您可以使用mongoimport工具将JSON数据导入到MongoDB中。
mongoimport --collection collection --file collection.json
这并不回答这个问题。请注意,问题的第二行表示他希望这样做*将数据导入数据库*。 – josh3736 2012-08-08 23:28:28
josh3736,你是对的。我更新我的答案。 – 2012-08-08 23:39:56
正如我在想,这将是有趣的写流JSON解析器,我也想,也许我应该做一个快速的搜索,看看是否有一个已经可用。
原来有。
- JSONStream“流JSON.parse和字符串化”
因为我只是觉得,我明明没有使用它,所以我不能在它的质量发表评论,但我有兴趣听听它是否有效。
它的工作考虑以下的CoffeeScript:
stream.pipe(JSONStream.parse('*'))
.on 'data', (d) ->
console.log typeof d
console.log "isString: #{_.isString d}"
这将记录的对象,因为他们进来,如果流是对象的数组。因此,被缓冲的唯一东西是一次一个对象。
随着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
这大部分是真实有用的,但我认为你需要'解析('*')'或者你不会得到任何数据。 – 2014-10-02 02:42:16
@JohnZwinck谢谢,已经更新了答案,并添加了一个工作示例来充分展示它。 – arcseldon 2014-10-02 11:23:04
在第一个代码块中,第一组圆括号'var getStream()= function(){'应该被删除。 – givemesnacks 2015-07-30 16:20:37
我使用split npm module解决了这个问题。将流分成两部分,它将“分解流并重新组装,以便每行都是一个块”。
示例代码:
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);
// ...
});
我有类似的要求,我需要读取数据块的节点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();
});
}
我知道你想避免读取整个JSON文件到内存如果可能的话,但是如果你有可用时,它可能不是一个坏主意性能明智的记忆。在json文件上使用node.js的require()可以非常快地将数据加载到内存中。
我运行了两个测试,看看在从81MB geojson文件中打印出每个功能的属性后,性能如何。
在第一次测试中,我使用var data = require('./geo.json')
将整个geojson文件读入内存。这花了3330毫秒,然后从每个特征中打印出一个属性花费了804毫秒,总共4134毫秒。但是,似乎node.js使用了411MB的内存。
在第二个测试中,我使用了@ arcseldon的回答JSONStream +事件流。我修改了JSONPath查询来选择我所需要的。这次内存永远不会超过82MB,但是现在整个过程需要70秒才能完成!
如果您可以控制输入文件,并且它是一组对象,则可以更轻松地解决此问题。安排输出与每条记录在一条线上的文件,如下所示:
[
{"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!
}
对于格式B,您可以通过块解析新行,并提取每个整行,如果在中间切断,则连接其余行。 虽然可能有更优雅的方式。我没有用过很多流。 – travis 2012-08-08 22:39:41