在 node.js 中一次读取一行文件?

IT技术 javascript node.js file-io lazy-evaluation
2021-01-17 17:38:31

我试图一次读取一个大文件。在 Quora 上发现了一个关于这个主题的问题,但我缺少一些联系来使整个事情融合在一起。

 var Lazy=require("lazy");
 new Lazy(process.stdin)
     .lines
     .forEach(
          function(line) { 
              console.log(line.toString()); 
          }
 );
 process.stdin.resume();

我想弄清楚的是如何一次从文件中读取一行,而不是像本示例中那样从 STDIN 读取。

我试过:

 fs.open('./VeryBigFile.csv', 'r', '0666', Process);

 function Process(err, fd) {
    if (err) throw err;
    // DO lazy read 
 }

但它不起作用。我知道在紧要关头我可以退回到使用 PHP 之类的东西,但我想弄清楚这一点。

我不认为另一个答案会起作用,因为该文件比我运行它的服务器的内存大得多。

6个回答

从 Node.js v0.12 和 Node.js v4.0.0 开始,有一个稳定的readline核心module。这是从文件中读取行的最简单方法,无需任何外部module:

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

或者:

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream('file.in')
});

lineReader.on('line', function (line) {
  console.log('Line from file:', line);
});

最后一行被正确读取(从 Node v0.12 或更高版本开始),即使没有 final \n

更新:此示例已添加到 Node 的 API 官方文档中

如何确定最后一行?通过捕捉“关闭”事件:rl.on('close', cb)
2021-03-14 17:38:31
您需要在 createInterface 定义中使用 terminal:false
2021-03-26 17:38:31
我认为github.com/jahewson/node-byline是逐行阅读的最佳实现,但意见可能会有所不同。
2021-03-27 17:38:31
Readline 的目的与GNU Readline类似而不是逐行读取文件。使用它读取文件有几个注意事项,这不是最佳实践。
2021-03-31 17:38:31
@Nakedible:有趣。你能用更好的方法发布答案吗?
2021-04-03 17:38:31

对于这样一个简单的操作,不应该依赖第三方module。放轻松。

var fs = require('fs'),
    readline = require('readline');

var rd = readline.createInterface({
    input: fs.createReadStream('/path/to/file'),
    output: process.stdout,
    console: false
});

rd.on('line', function(line) {
    console.log(line);
});
这个解决方案有问题。如果你使用 your.js <lines.txt 你不会得到最后一行。如果它最终没有 '\n' 。
2021-03-15 17:38:31
readline对于有经验的 Unix/Linux 程序员来说,这个包的行为方式确实很奇怪。
2021-03-22 17:38:31
“最后一个 \n 之后的数据”问题似乎在我的节点版本 (0.12.7) 中得到解决。所以我更喜欢这个答案,它看起来最简单也最优雅。
2021-03-24 17:38:31
rd.on("close", ..); 可以用作回调(在读取所有行时发生)
2021-03-26 17:38:31
遗憾的是,这个有吸引力的解决方案无法正常工作——line事件仅在点击 之后才会出现\n,即,所有替代方案都被遗漏了(参见unicode.org/reports/tr18/#Line_Boundaries)。#2,最后一个之后的数据\n被静默忽略(参见stackoverflow.com/questions/18450197/...)。我认为这个解决方案是危险的,因为它适用于 99% 的所有文件和 99% 的数据,但其余部分无声无息地失败无论何时,fs.writeFileSync( path, lines.join('\n'))您都编写了一个文件,该文件只能被上述解决方案部分读取。
2021-04-03 17:38:31

您不必使用open该文件,而是必须创建一个ReadStream.

fs.createReadStream

然后将该流传递给 Lazy

这个结果在搜索结果上的排名很高,所以值得注意的是,Lazy 看起来被遗弃了。已经 7 个月没有任何变化了,并且有一些可怕的错误(最后一行被忽略,大量内存泄漏等)。
2021-03-12 17:38:31
@Cecchi 和 @Max,不要使用 join,因为它会在内存中缓冲整个文件。相反,只需收听 'end' 事件:new lazy(...).lines.forEach(...).on('end', function() {...})
2021-03-13 17:38:31
@Max,试试: new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
2021-03-21 17:38:31
有没有类似于 Lazy 的结束事件?当所有行都读入时?
2021-03-26 17:38:31
@Cecchi、@Corin 和 @Max:就其value而言,我.on('end'... 在 之后 让自己发疯了.forEach(...),实际上当我首先绑定事件时,一切都按预期运行
2021-04-06 17:38:31
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
这将读取内存中整个文件,然后将其拆分为行。这不是问题所问的。关键是能够按需顺序读取大文件。
2021-03-27 17:38:31
这可能无法回答最初的问题,但如果它符合您的内存限制,它仍然很有用。
2021-03-28 17:38:31
这适合我的用例,我正在寻找一种简单的方法将输入从一个脚本转换为另一种格式。谢谢!
2021-03-30 17:38:31

2019年更新

一个很棒的例子已经发布在官方的 Nodejs 文档中。这里

这需要在您的机器上安装最新的 Nodejs。>11.4

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();
由于其基于Promise的行为,这个答案比上面的任何东西都要好得多,特别表明了 EOF。
2021-03-11 17:38:31
也许这对其他人来说是显而易见的,但我花了一些时间来调试:如果awaitcreateInterface()调用和for await循环开始之间有任何s ,您将神秘地从文件开始丢失行。createInterface()立即开始在幕后发出行,隐式创建的异步迭代器在创建之前const line of rl无法开始侦听这些行。
2021-03-26 17:38:31
谢谢,好甜
2021-04-08 17:38:31