Javascript 在节点中高效地逐行读取文件
我已经学会了用它逐行读取文件,例如Javascript 在节点中高效地逐行读取文件,javascript,node.js,Javascript,Node.js,我已经学会了用它逐行读取文件,例如 readline .createInterface({input: fs.createReadStream('xxx')}) .on('line', (line) => { apply_regexp_on_line }) .on('close', () => { report_all_regexps }); 但是,这相当慢,因为我比较了grep和JavaScript regexp的性能,后者在我测试的regexp上有更好的
readline
.createInterface({input: fs.createReadStream('xxx')})
.on('line', (line) => { apply_regexp_on_line })
.on('close', () => { report_all_regexps });
但是,这相当慢,因为我比较了grep
和JavaScript regexp的性能,后者在我测试的regexp上有更好的性能。(请参阅)因此,我认为我必须归咎于节点异步读取线
在我的情况下,我根本不关心异步,我只需要利用JavaScript中的快速regexp来处理非常大的日志文件(通常为1-2GB,有时高达10GB)。这样做的最佳方式是什么?我唯一关心的是速度
优点:一些日志文件是gzip文件,所以我需要解压缩它们。如果有人能为我推荐一款快速逐行阅读纯文本和压缩文本的阅读器,我将不胜感激。这与您的数据相比如何
// module linegrep.js
'use strict';
var through2 = require('through2');
var StringDecoder = require('string_decoder').StringDecoder
function grep(regex) {
var decoder = new StringDecoder('utf8'),
last = "",
lineEnd = /\r?\n/;
var stream = through2({}, function transform(chunk, enc, cb) {
var lines = decoder.write(last + chunk).split(lineEnd), i;
last = lines.pop();
for (i = 0; i < lines.length; i++) {
if (regex.test(lines[i])) this.push(lines[i]);
}
cb();
}, function flush(cb) {
if (regex.test(last)) this.push(last);
cb();
});
stream._readableState.objectMode = true;
return stream;
}
module.exports = grep;
这会将文件流转换为行的对象流,通过正则表达式映射它们,并仅返回匹配的行。不确定在行上应用正则表达式做什么,但是否可以使用unix
sed
程序替换字符串?很快。可能可以编写一个快速简单的shell脚本来进行解压缩和sed'ing。请参阅问题中的基准测试链接sed
不如JavaScript快。基本上,apply_regexp_on_line
将使用regexp捕获日志文件中的一些文本并存储它,report_all_regexp
将以给定的格式报告捕获的文本。@xis您确定没有在基准测试中计时新进程的分叉吗<如果您考虑到必须读取文件并生成输出,那么code>sed和grep
将比JS快得多。如果您的JS测试没有对I/O部分计时(看起来好像没有:它会将数据读入内存并根据该部分运行基准测试),那么几乎不可能进行公平的比较。@VsevolodGoloviznin我只是这样做了,但日志文件的大小较小,只有128MB,因为我的基准测试代码使用的是JavaScript缓冲区,限制为192MB。grep耗时0.41秒,node耗时0.32秒。我用/usr/bin/time
而不是我的代码来计算时间。我的regexp是boot\([a-z]+)\u head\。
,两者都有57654个捕获。@SamuelToh请查看Tomalak的答案。至少我们现在对grep的性能是一样的。忘记提到基准数据:grep花了约1.2秒,而grep花了0.42秒,toString代码用了0.32秒。这要快得多,对egrep的性能是一样的,都是3.1秒(我使用的是另一个文件/计算机,所以结果可能不同)。好吧,基本上你没有错。通过将第一种方法中的多个管道(拆分->映射->过滤器)融合到一个管道中,我确实删除了相当多的事件传递。我还重写了split代码(最初来自split2npm模块),在这个过程中对其进行了一些优化。我猜上面代码中的last
处理仍然有一点效率低下,如果你想让它变得更智能,也许它可以帮助你再获得几毫秒。@xis-Oh,并且不解码输入,因为utf8也有影响。如果日志文件使用单字节编码,请相应地配置代码。当然,如果您在此处做出错误的假设,您可能会损坏数据。将/\r?\n/替换为“\n”将进一步提高性能,仅供参考。我确信我正在使用UNIX newline。
// index.js
'use strict';
var fs = require('fs');
var zlib = require('zlib');
var grep = require('./linegrep');
function grepFile(filename, regex) {
var rstream = fs.createReadStream(filename, {highWaterMark: 172 * 1024});
if (/\.gz$/.test(filename)) rstream = rstream.pipe(zlib.createGunzip());
return rstream
.pipe(grep(regex));
}
// -------------------------------------------------------------------------
var t = Date.now(), mc = 0;
grepFile('input.txt', /boot\.([a-z]+)_head\./).on('data', function (line) {
mc++;
console.log(line);
}).on('end', function () {
console.log( mc + " matches, " + (Date.now() - t) + " ms" );
});