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

Ale*_*x C 517 javascript file-io lazy-evaluation node.js

我试图一次读一行大文件.我在Quora上发现了一个关于这个问题的问题,但是我错过了一些联系,以使整个事情融合在一起.

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

我想弄清楚的是我如何从文件而不是STDIN一次读取一行,如本示例所示.

我试过了:

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

 function Process(err, fd) {
    if (err) throw err;
    // DO lazy read 
 }
Run Code Online (Sandbox Code Playgroud)

但它不起作用.我知道,在一个紧要关头,我可以回到使用像PHP这样的东西,但我想弄清楚这一点.

我认为其他答案不会起作用,因为文件比我正在运行它的服务器大得多.

Dan*_*scu 729

自Node.js v0.12和Node.js v4.0.0以来,有一个稳定的readline核心模块.这是从文件中读取行的最简单方法,没有任何外部模块:

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

lineReader.on('line', function (line) {
  console.log('Line from file:', line);
});
Run Code Online (Sandbox Code Playgroud)

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

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

  • 如何确定最后一行?通过捕捉"关闭"事件:`rl.on('close',cb)` (59认同)
  • Readline用于与[GNU Readline](https://cnswww.cns.cwru.edu/php/chet/readline/rltop.html)类似的目的,*not*用于逐行读取文件.使用它来读取文件有几点需要注意,这不是最佳实践. (25认同)
  • @Nakedible:有趣.你能用更好的方法发布答案吗? (8认同)
  • 你需要一个终端:在createInterface定义中为false (7认同)
  • 我认为https://github.com/jahewson/node-byline是逐行阅读的最佳实现,但意见可能会有所不同. (5认同)
  • 有没有办法获得行号? (3认同)
  • 在抓住下一行之前,如何限制这个或者至少允许函数回调? (2认同)
  • @PitiOngmongkolkul 在顶部使用 `var count = 0;`,在 `on('line')` 处理程序的顶部使用 `count++;` @Ryan 在 `on 的顶部使用 lineReader.pause() ('line')` 处理程序并在您准备好继续时使用 lineReader.resume()。 (2认同)

kof*_*asa 158

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

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);
});
Run Code Online (Sandbox Code Playgroud)

  • 遗憾的是,这个有吸引力的解决方案无法正常工作 - 'line`事件只有在击中`\n`之后才会出现,即所有替代方案都被遗漏(参见http://www.unicode.org/reports/tr18/#Line_Boundaries) .#2,最后一个`\n`后的数据被静默忽略(参见http://stackoverflow.com/questions/18450197/nodejs-readline-missing-last-line-of-file).我称这个解决方案是危险的*因为它适用于99%的所有文件和99%的数据,但**无声地失败**其余部分.每当你执行`fs.writeFileSync(path,lines.join('\n'))时,你就编写了一个文件,只能通过上述解决方案部分读取. (30认同)
  • `rd.on("close",..);`可以用作回调(在读取所有行时发生) (9认同)
  • 在我的版本的节点(0.12.7)中似乎解决了"最后一个问题之后的数据"问题.所以我更喜欢这个答案,这似乎是最简单和最优雅的. (5认同)
  • 此解决方案存在问题.如果您使用your.js <lines.txt,则不会获得最后一行.如果它在最后没有'\n'. (4认同)

Ray*_*nos 63

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

fs.createReadStream

然后将该流传递给 Lazy

  • 这个结果对搜索结果非常高,因此值得注意的是Lazy看起来已经放弃了.它已经有7个月没有任何变化,并且有一些可怕的错误(最后一行被忽略,大量内存泄漏等). (51认同)
  • @Cecchi和@Max,不要使用join,因为它会将整个文件缓冲在内存中.相反,只需听取'结束'事件:`new lazy(...).lines.forEach(...).on('end',function(){...})` (6认同)
  • @Cecchi,@ Corin和@Max:对于它的价值,我开车疯狂地链接`.on('end'...`_ after_` .forEach(...)`,实际上一切都表现得像预期的那样我绑定了事件_first_. (3认同)
  • 是否有类似懒惰的终结事件?什么线都被读入? (2认同)

pol*_*tto 36

有一个非常好的模块用于逐行读取文件,它被称为行读取器

有了它,你只需写:

var lineReader = require('line-reader');

lineReader.eachLine('file.txt', function(line, last) {
  console.log(line);
  // do whatever you want with line...
  if(last){
    // or check if it's the last one
  }
});
Run Code Online (Sandbox Code Playgroud)

如果需要更多控制,你甚至可以使用"java风格"界面迭代文件:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

  • 这很好用.它甚至读取最后一行(!).值得一提的是,如果它是一个Windows风格的文本文件,它会保留\ r \n.line.trim()可以删除额外的\ r \n. (4认同)
  • 同时,有一种内置方法可以使用 [`readline` 核心模块](http://stackoverflow.com/a/32599033/1269037) 从文件中读取行。 (2认同)
  • 只是为了记录,`line-reader` 异步读取文件。它的同步替代方案是`line-reader-sync` (2认同)

Joh*_*ams 33

require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
Run Code Online (Sandbox Code Playgroud)

  • 这将读取内存中的*整个文件*,然后将其拆分为行.这不是问题所要求的.关键是能够按需顺序读取大文件. (38认同)
  • 这可能无法回答原来的问题,但如果它适合您的内存限制,那么它仍然很有用。 (3认同)
  • 这适合我的用例,我在寻找一种简单的方法将输入从一个脚本转换为另一种格式。谢谢! (2认同)

nf0*_*590 19

老话题,但这有效:

var rl = readline.createInterface({
      input : fs.createReadStream('/path/file.txt'),
      output: process.stdout,
      terminal: false
})
rl.on('line',function(line){
     console.log(line) //or parse line
})
Run Code Online (Sandbox Code Playgroud)

简单.无需外部模块.

  • 这个答案是[早期答案的确切欺骗](http://stackoverflow.com/a/15554600/1028230),但没有评论警告[readline软件包标记为不稳定](http://stackoverflow.com/ questions/6156501/read-a-file-one-line-at-a-time-in-node-js#comment22523025_15554600)(截至2015年4月仍然不稳定),并且,在2013年中期,[无法阅读最后一行没有行结尾的文件](http://stackoverflow.com/questions/6156501/read-a-file-one-line-at-a-time-in-node-js#comment27147074_15554600).最后一行问题出现在我第一次在v0.10.35中使用时,然后就消失了./哎呀 (12认同)
  • 如果你得到'readline未定义'或'fs未定义',则添加`var readline = require('readline');`和`var fs = require('fs');`以使其工作.否则甜蜜,甜蜜的代码.谢谢. (2认同)

Ern*_*lli 18

您可以随时滚动自己的线路阅读器.我还没有对此片段进行基准测试,但它正确地将传入的块流分成不带尾随'\n'的行

var last = "";

process.stdin.on('data', function(chunk) {
    var lines, i;

    lines = (last+chunk).split("\n");
    for(i = 0; i < lines.length - 1; i++) {
        console.log("line: " + lines[i]);
    }
    last = lines[i];
});

process.stdin.on('end', function() {
    console.log("line: " + last);
});

process.stdin.resume();
Run Code Online (Sandbox Code Playgroud)

我在处理日志解析期间需要累积数据的快速日志解析脚本时确实想到了这一点,我觉得尝试使用js和节点而不是使用perl或bash这样做会很好.

无论如何,我确实认为小nodejs脚本应该是自包含的而不是依赖于第三方模块,所以在阅读了这个问题的所有答案后,每个人都使用各种模块来处理行解析,13 SLOC本机nodejs解决方案可能会引起关注.

  • @hippietrail你可以使用`fs.createReadStream('./ myBigFile.csv')`创建一个`ReadStream`并使用它代替`stdin` (3认同)
  • 每个块是否保证只包含完整的行?是否保证多字节UTF-8字符不会在块边界处分割? (2认同)

Lea*_*per 16

2019年更新

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

这需要在您的计算机上安装最新的Node.js。> 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();
Run Code Online (Sandbox Code Playgroud)

  • 也许这对其他人来说是显而易见的,但我花了一段时间来调试:如果在“createInterface()”调用和“for wait”循环开始之间有任何“await”,您将神秘地丢失来自文件的开头。`createInterface()` 立即开始在幕后发出行,并且使用 `const line of rl` 隐式创建的异步迭代器在创建之前无法开始侦听这些行。 (6认同)
  • 这个答案比上面的任何答案都要好得多,因为它基于承诺的行为,明确地表明了 EOF。 (3认同)

Lar*_*rry 13

Node.js v18.11.0 新增了逐行读取文件的功能

  • filehandle.readLines([选项])

这就是您如何将其与您想要读取的文本文件一起使用

import { open } from 'node:fs/promises';
myFileReader();
async function myFileReader() {
    const file = await open('./TextFileName.txt');
    for await (const line of file.readLines()) {
        console.log(line)
    }
}
Run Code Online (Sandbox Code Playgroud)

要了解更多信息,请阅读 Node.js 文档,这里是文件系统 readlines() 的链接: https: //nodejs.org/api/fs.html#filehandlereadlinesoptions

  • 很好的发现,这使我免于陷入其他一些答案的陷阱(例如 CRLF 行结尾、读取最后一行、提前正确关闭读取流)。谢谢!! (3认同)

Tou*_*ouv 12

使用运营商模块:

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});
Run Code Online (Sandbox Code Playgroud)


j03*_*03m 8

http结果是由于节点中的排放/暂停/恢复方式工作而使用Lazy逐行读取然后处理这些行并将它们写入另一个流时,最终导致了大量的大量内存泄漏(请参阅:http:// elegantcode .com/2011/04/06/take-baby-steps-with-node-js-pumping-data-between-streams /(我喜欢这个家伙顺便说一句)).我没有仔细查看Lazy究竟为什么要理解,但我无法暂停我的读取流以允许在没有Lazy退出的情况下消耗.

我编写了将大量csv文件处理成xml文档的代码,你可以在这里看到代码:https://github.com/j03m/node-csv2xml

如果您使用Lazy line运行以前的修订版,则会泄漏.最新版本根本没有泄漏,你可以将它作为阅读器/处理器的基础.虽然我有一些自定义的东西.

编辑:我想我还应该注意我的Lazy代码工作正常,直到我发现自己编写了足够大的xml片段,因为必要而耗尽/暂停/恢复.对于较小的块,它很好.


Gab*_*mas 8

编辑:

使用转换流.


使用BufferedReader,您可以读取行.

new BufferedReader ("lorem ipsum", { encoding: "utf8" })
    .on ("error", function (error){
        console.log ("error: " + error);
    })
    .on ("line", function (line){
        console.log ("line: " + line);
    })
    .on ("end", function (){
        console.log ("EOF");
    })
    .read ();
Run Code Online (Sandbox Code Playgroud)


nf0*_*590 7

自从发布我的原始答案以来,我发现split是一个非常容易使用的节点模块,用于文件中的行读取; 其中也接受可选参数.

var split = require('split');
fs.createReadStream(file)
    .pipe(split())
    .on('data', function (line) {
      //each chunk now is a seperate line! 
    });
Run Code Online (Sandbox Code Playgroud)

没有在非常大的文件上测试过.如果你这样做,请告诉我们.


pan*_*a82 6

我对此缺乏全面的解决方案感到沮丧,所以我把自己的尝试放在一起(git/npm).复制粘贴的功能列表:

  • 交互式线路处理(基于回调,无需将整个文件加载到RAM中)
  • (可选)返回数组中的所有行(详细或原始模式)
  • 交互式中断流式传输,或执行地图/过滤器之类的处理
  • 检测任何换行惯例(PC/Mac/Linux)
  • 正确的eof /最后一线治疗
  • 正确处理多字节UTF-8字符
  • 以每行为基础检索字节偏移量和字节长度信息
  • 随机访问,使用基于行或基于字节的偏移
  • 自动映射行偏移信息,以加快随机访问
  • 零依赖
  • 测试

NIH?你决定 :-)


use*_*097 5

function createLineReader(fileName){
    var EM = require("events").EventEmitter
    var ev = new EM()
    var stream = require("fs").createReadStream(fileName)
    var remainder = null;
    stream.on("data",function(data){
        if(remainder != null){//append newly received data chunk
            var tmp = new Buffer(remainder.length+data.length)
            remainder.copy(tmp)
            data.copy(tmp,remainder.length)
            data = tmp;
        }
        var start = 0;
        for(var i=0; i<data.length; i++){
            if(data[i] == 10){ //\n new line
                var line = data.slice(start,i)
                ev.emit("line", line)
                start = i+1;
            }
        }
        if(start<data.length){
            remainder = data.slice(start);
        }else{
            remainder = null;
        }
    })

    stream.on("end",function(){
        if(null!=remainder) ev.emit("line",remainder)
    })

    return ev
}


//---------main---------------
fileName = process.argv[2]

lineReader = createLineReader(fileName)
lineReader.on("line",function(line){
    console.log(line.toString())
    //console.log("++++++++++++++++++++")
})
Run Code Online (Sandbox Code Playgroud)

  • @hippietrail:对于UTF-8,答案是否定的,即使它正在处理字节流而不是字符流.它打破换行符(0x0a).在UTF-8中,多字节字符的所有字节都设置了高位.因此,没有多字节字符可以包括嵌入的换行符或其他常见的ASCII字符.然而,UTF-16和UTF-32是另一回事. (2认同)

hip*_*ail 5

我想解决同样的问题,基本上在 Perl 中是这样的:

while (<>) {
    process_line($_);
}
Run Code Online (Sandbox Code Playgroud)

我的用例只是一个独立的脚本,而不是服务器,所以同步很好。这些是我的标准:

  • 可以在许多项目中重用的最小同步代码。
  • 对文件大小或行数没有限制。
  • 线的长度没有限制。
  • 能够处理 UTF-8 中的完整 Unicode,包括 BMP 之外的字符。
  • 能够处理 *nix 和 Windows 行尾(我不需要旧式 Mac)。
  • 要包含在行中的行尾字符。
  • 能够处理带有或不带有行尾字符的最后一行。
  • 不使用 node.js 发行版中未包含的任何外部库。

这是一个让我感受 node.js 中的低级脚本类型代码并决定它作为其他脚本语言(如 Perl)替代品的可行性的项目。

经过惊人的努​​力和几次错误的启动,这是我想出的代码。它非常快,但比我预期的要简单:(在 GitHub 上分叉)

var fs            = require('fs'),
    StringDecoder = require('string_decoder').StringDecoder,
    util          = require('util');

function lineByLine(fd) {
  var blob = '';
  var blobStart = 0;
  var blobEnd = 0;

  var decoder = new StringDecoder('utf8');

  var CHUNK_SIZE = 16384;
  var chunk = new Buffer(CHUNK_SIZE);

  var eolPos = -1;
  var lastChunk = false;

  var moreLines = true;
  var readMore = true;

  // each line
  while (moreLines) {

    readMore = true;
    // append more chunks from the file onto the end of our blob of text until we have an EOL or EOF
    while (readMore) {

      // do we have a whole line? (with LF)
      eolPos = blob.indexOf('\n', blobStart);

      if (eolPos !== -1) {
        blobEnd = eolPos;
        readMore = false;

      // do we have the last line? (no LF)
      } else if (lastChunk) {
        blobEnd = blob.length;
        readMore = false;

      // otherwise read more
      } else {
        var bytesRead = fs.readSync(fd, chunk, 0, CHUNK_SIZE, null);

        lastChunk = bytesRead !== CHUNK_SIZE;

        blob += decoder.write(chunk.slice(0, bytesRead));
      }
    }

    if (blobStart < blob.length) {
      processLine(blob.substring(blobStart, blobEnd + 1));

      blobStart = blobEnd + 1;

      if (blobStart >= CHUNK_SIZE) {
        // blobStart is in characters, CHUNK_SIZE is in octets
        var freeable = blobStart / CHUNK_SIZE;

        // keep blob from growing indefinitely, not as deterministic as I'd like
        blob = blob.substring(CHUNK_SIZE);
        blobStart -= CHUNK_SIZE;
        blobEnd -= CHUNK_SIZE;
      }
    } else {
      moreLines = false;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

它可能可以进一步清理,这是反复试验的结果。


Dor*_*ian 5

在大多数情况下,这应该足够了:

const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, file) => {
  const lines = file.split('\n')

  for (let line of lines)
    console.log(line)
});
Run Code Online (Sandbox Code Playgroud)