假设您有一个巨大的(> 1GB)记录IDCSV:
655453
4930285
493029
4930301
493031
...
对于每个id
,您希望进行 REST API 调用来获取记录数据,在本地转换它,然后将其插入到本地数据库中。
如何使用 Node.js 的可读Stream
做到这一点?
我的问题基本上是这样的:你如何逐行读取一个非常大的文件,为每一行运行一个异步函数,并且[可选]能够从特定行开始读取文件?
从以下 Quora 问题中,我开始学习使用fs.createReadStream
:
http://www.quora.com/What-is-the-best-way-to-read-a-file-line-by-line-in-node-js
var fs = require('fs');
var lazy = require('lazy');
var stream = fs.createReadStream(path, {
flags: 'r',
encoding: 'utf-8'
});
new lazy(stream).lines.forEach(function(line) {
var id = line.toString();
// pause stream
stream.pause();
// make async API call...
makeAPICall(id, function() {
// then resume to process next id
stream.resume();
});
});
但是,该伪代码不起作用,因为lazy
模块强制您读取整个文件(作为流,但没有暂停)。所以这种方法似乎行不通。
另一件事是,我希望能够从特定行开始处理此文件。这样做的原因是,处理每个id
(进行 api 调用、清理数据等)每条记录最多可能需要半秒,所以我不想每次都从文件的开头开始。我正在考虑使用的天真方法是只捕获最后一个处理的 id 的行号,然后保存它。然后,当您再次解析文件时,逐行流式传输所有 ID,直到找到您离开的行号,然后执行makeAPICall
业务。另一种天真的方法是写入小文件(例如 100 个 id)并一次处理一个文件(足够小的数据集,可以在没有 IO 流的情况下在内存中执行所有操作)。有没有更好的方法可以做到这一点?
我可以看到这如何变得棘手(以及节点惰性出现的地方),因为 stream.on('data', function(chunk) {});
中的chunk
可能只包含一行的一部分(如果 bufferSize 很小,每个块可能是 10 行,但由于id
是可变长度,它可能只有 9.5 行或其他)。这就是为什么我想知道上述问题的最佳方法是什么。
与 Andrew Андрей Листочкин 的答案相关:
您可以使用像 byline 这样的模块来获取每行的单独data
事件。它是围绕原始文件流的转换流,为每个块生成一个data
事件。这使您可以在每行之后暂停。
byline
不会像lazy
那样将整个文件读入内存。
var fs = require('fs');
var byline = require('byline');
var stream = fs.createReadStream('bigFile.txt');
stream.setEncoding('utf8');
// Comment out this line to see what the transform stream changes.
stream = byline.createStream(stream);
// Write each line to the console with a delay.
stream.on('data', function(line) {
// Pause until we're done processing this line.
stream.pause();
setTimeout(() => {
console.log(line);
// Resume processing.
stream.resume();
}, 200);
});
我想你不需要使用node-lazy
.这是我在 Node 文档中找到的内容:
事件:
data
function (data) { }
如果出现以下情况,则
data
事件发出Buffer
(默认情况下)或string
使用了setEncoding()
。
因此,这意味着您在流上调用setEncoding()
,那么您的data
事件回调将接受字符串参数。然后在此回调中,您可以调用 use .pause()
和 .resume()
方法。
伪代码应如下所示:
stream.setEncoding('utf8');
stream.addListener('data', function (line) {
// pause stream
stream.pause();
// make async API call...
makeAPICall(line, function() {
// then resume to process next line
stream.resume();
});
})
尽管文档没有明确指定逐行读取流,但我假设文件流就是这种情况。至少在其他语言和平台中,文本流以这种方式工作,我认为 Node 流没有理由有所不同。