这是我最喜欢的浏览文件的方式,它是一种简单的本地解决方案,用现代的async/await
进行渐进式(不是“ slurp”或全内存方式)文件读取。当处理大型文本文件时,我发现这是一种“自然”的解决方案,而无需使用readline
软件包或任何非核心依赖项。
let buf = '';
for await ( const chunk of fs.createReadStream('myfile') ) {
const lines = buf.concat(chunk).split(/\r?\n/);
buf = lines.pop() ?? '';
for( const line of lines ) {
console.log(line);
}
}
if(buf.length) console.log(buf);
您可以在
fs.createReadStream
中调整编码或使用
chunk.toString(<arg>)
。这样可以更好地微调行拆分以适应您的需求,例如使用
.split(/\n+/)
跳过空行,并使用
fs.createReadStream('myfile', { highWaterMark: <chunkSize> })
控制块大小。
不要忘记创建一个像
processLine(line)
这样的函数,以避免由于剩余的
buf
而重复处理代码两次。不幸的是,在此设置中,
ReadStream
实例不会更新其文件结束标志,因此没有办法(据我所知)在循环中检测到我们处于最后一次迭代,除非使用一些更冗长的技巧,如使用
fs.Stats()
将文件大小与
.bytesRead
进行比较。因此,最终的
buf
处理解决方案是必要的,除非您绝对确定您的文件以换行符
\n
结尾,在这种情况下,
for await
循环就足够了。
性能考虑
块大小对性能至关重要,默认值为64k,对于多MB文件,较大的块可以将速度提高
一个数量级。
上述片段的运行速度至少与基于NodeJS v18的
fs.readLine()
或基于
readline
模块的代码(接受的答案)相同,一旦您将
highWaterMark
调整为您的机器可以处理的内容,即
如果您的可用内存允许,将其设置为与文件相同的大小是最快的。
在任何情况下,这里的任何NodeJS逐行阅读答案都比Perl或本地*Nix解决方案慢一个数量级。
类似的替代方案
★ 如果您喜欢事件驱动的异步版本,则应该是:
let buf = '';
fs.createReadStream('myfile')
.on('data', chunk => {
const lines = buf.concat(chunk).split(/\r?\n/);
buf = lines.pop();
for( const line of lines ) {
console.log(line);
}
})
.on('end', () => buf.length && console.log(buf) );
★ 如果您不介意导入stream
核心包,那么这就是等效的管道流版本,它允许链接转换,如gzip解压缩:
const { Writable } = require('stream');
let buf = '';
fs.createReadStream('myfile').pipe(
new Writable({
write: (chunk, enc, next) => {
const lines = buf.concat(chunk).split(/\r?\n/);
buf = lines.pop();
for (const line of lines) {
console.log(line);
}
next();
}
})
).on('finish', () => buf.length && console.log(buf) );
fs.readSync()
会发现处理起来非常困难。你可以将二进制八位字节读入缓冲区,但是在将其转化为 JavaScript 字符串并扫描 EOLs(行结束符)之前,没有简单的方法来处理部分 UTF-8 或 UTF-16 字符。Buffer()
类型没有如本地字符串一样丰富的函数集来操作其实例,但是本地字符串无法包含二进制数据。在我看来,缺少从任意文件句柄读取文本行的内置方式是 node.js 中的一个真正差距。 - hippietrailif (line.length==1 && line[0] == 48) special(line);
,以处理这种情况。 - Thabonode
的 API 文档中略有修改,详情请见 https://github.com/nodejs/node/pull/4609。 - eljefedelrodeodeljefe