Node.JS中的createReadStream

10 javascript file stream readfile node.js

所以我用fs.readFile()给了我

"致命错误:CALL_AND_RETRY_LAST分配失败 - 处理内存不足"

因为fs.readFile()在调用回调之前将整个文件加载到内存中,我应该使用fs.createReadStream()吗?

这就是我以前用readFile做的事情:

fs.readFile('myfile.json', function (err1, data) {
    if (err1) {
        console.error(err1);
    } else {
        var myData = JSON.parse(data);
        //Do some operation on myData here
    }
}
Run Code Online (Sandbox Code Playgroud)

对不起,我对流媒体不熟悉; 以下是使用流媒体做同样事情的正确方法吗?

var readStream = fs.createReadStream('myfile.json');

readStream.on('end', function () {  
    readStream.close();
    var myData = JSON.parse(readStream);
    //Do some operation on myData here
});
Run Code Online (Sandbox Code Playgroud)

谢谢

Che*_*hev 16

如果文件是巨大的那么是,流式传输将是您想要处理它的方式.但是,您在第二个示例中所做的是让流将所有文件数据缓冲到内存中,然后再处理它end.它与readFile这种方式基本上没有什么不同.

你想看看JSONStream.流式传输意味着您希望在流过时处理数据.在你的情况下,你显然必须这样做,因为你无法一次性将整个文件缓冲到内存中.考虑到这一点,希望这样的代码是有道理的:

JSONStream.parse('rows.*.doc')
Run Code Online (Sandbox Code Playgroud)

请注意,它有一种查询模式.那是因为你不会让文件中的整个JSON对象/数组同时使用所有JSON对象/数组,所以你必须考虑JSONStream在找到它时如何处理数据.

您可以使用JSONStream来实质查询您感兴趣的JSON数据.这样您就不会将整个文件缓冲到内存中.它确实有缺点,如果你确实需要所有数据,那么你将不得不多次流式传输文件,使用JSONStream只提取你当时所需的数据,但在你的情况下,你没有很多选择.

您还可以使用JSONStream按顺序解析数据并执行将其转储到数据库中的操作.

JSONStream.parse类似于JSON.parse但它不是返回整个对象而是返回一个流.当解析流获得足够的数据以形成与查询匹配的整个对象时,它将发出一个data事件,其中数据是与您的查询匹配的文档.一旦配置了数据处理程序,就可以将读取流传输到解析流中并观察魔术的发生.

例:

var JSONStream = require('JSONStream');
var readStream = fs.createReadStream('myfile.json');
var parseStream = JSONStream.parse('rows.*.doc');
parseStream.on('data', function (doc) {
  db.insert(doc); // pseudo-code for inserting doc into a pretend database.
});
readStream.pipe(parseStream);
Run Code Online (Sandbox Code Playgroud)

这是帮助您了解正在发生的事情的冗长方式.这是一个更简洁的方式:

var JSONStream = require('JSONStream');
fs.createReadStream('myfile.json')
  .pipe(JSONStream.parse('rows.*.doc'))
  .on('data', function (doc) {
    db.insert(doc);
  });
Run Code Online (Sandbox Code Playgroud)

编辑:

为了进一步明确发生了什么,请尝试这样考虑.假设你有一个巨大的湖泊,你想要用水来净化它并将水移到一个新的水库.如果你有一个巨大的神奇直升机和一个巨大的水桶,那么你可以飞过湖面,将湖泊放入水桶,加入处理化学品,然后飞到目的地.

问题当然是没有这样的直升机可以处理那么多的重量或体积.这根本不可能,但这并不意味着我们不能以不同的方式实现我们的目标.因此,您将在湖泊和新水库之间建造一系列河流(溪流).然后,您可以在这些河流中设置净化站,净化通过它的任何水.这些站可以以各种方式操作.也许治疗可以做得如此之快,以至于你可以让河流自由流动,当水以最大速度沿着河流流动时,净化就会发生.

也可能需要一些时间来处理水,或者该站需要一定量的水才能有效地处理它.所以你设计你的河流有闸门,你控制从湖里流入河流的水流,让水站缓冲他们需要的水,直到他们完成工作并将净化水释放到下游并进入最后目的地.

这几乎就是您想要对数据做的事情.解析流是您的清理工作站,它会缓冲数据,直到它足以形成与您的查询匹配的整个文档,然后它将该数据推送到下游(并发出data事件).

节点流很好,因为大多数时候你不必处理打开和关闭门.当流缓冲一定量的数据时,节点流足够智能以控制回流.这就好像清洁站和湖上的大门正在互相交谈,以确定完美的流速.

如果您有一个流式数据库驱动程序,那么理论上您可以创建某种插入流,然后执行parseStream.pipe(insertStream)而不是data手动处理事件:D.以下是在另一个文件中创建JSON文件的过滤版本的示例.

fs.createReadStream('myfile.json')
  .pipe(JSONStream.parse('rows.*.doc'))
  .pipe(JSONStream.stringify())
  .pipe(fs.createWriteStream('filtered-myfile.json'));
Run Code Online (Sandbox Code Playgroud)

  • 没问题。如果可以使事情变得更清楚,我在答案的底部添加了一个类比。如果它对您有所帮助,请不要忘记接受答案:) (2认同)
  • 值得更多投票!很好的解释,尤其是那些“河流” (2认同)