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

时间:2011-05-27 18:49:48

标签: javascript node.js file-io lazy-evaluation

我试图一次读取一行大文件。我发现a question on 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这样的东西,但我想弄明白这一点。

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

29 个答案:

答案 0 :(得分:681)

自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);
});

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

更新:此示例为added to Node's API official documentation

答案 1 :(得分:156)

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

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);
});

答案 2 :(得分:63)

您不必open该文件,而是必须创建ReadStream

fs.createReadStream

然后将该流传递给Lazy

答案 3 :(得分:33)

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

用它你只需写:

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
  }
});

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

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

答案 4 :(得分:22)

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

答案 5 :(得分: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
})

简单。无需外部模块。

答案 6 :(得分: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();

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

无论如何,我觉得小nodejs脚本应该是自包含的,而不是依赖于第三方模块,所以在阅读了这个问题的所有答案后,每个人都使用各种模块来处理行解析,13 SLOC本地nodejs解决方案可能是感兴趣的。

答案 7 :(得分:12)

使用carrier module

var carrier = require('carrier');

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

答案 8 :(得分:8)

编辑:

使用transform stream


使用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 ();

答案 9 :(得分:8)

我最终得到了一个巨大的,大量的内存泄漏,使用Lazy逐行读取,然后尝试处理这些行并将其写入另一个流,因为节点中的排放/暂停/恢复工作方式(参见:{{ 3}}(我喜欢这个家伙))。我没有仔细查看Lazy究竟为什么要理解,但我无法暂停我的读取流以允许在没有Lazy退出的情况下消耗。

我编写了将大量csv文件处理成xml文档的代码,你可以在这里看到代码:http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/

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

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

答案 10 :(得分:7)

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

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

Haven未在非常大的文件上进行测试。如果你这样做,请告诉我们。

答案 11 :(得分:6)

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

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

NIH?你决定: - )

答案 12 :(得分: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)
});

答案 13 :(得分: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("++++++++++++++++++++")
})

答案 14 :(得分:4)

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

while (<>) {
    process_line($_);
}

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

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

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

经过一番惊人的努力和一些错误的开始,这就是我提出的代码。它比我预期的要快得多但不那么琐碎:(fork it on 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;
    }
  }
}

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

答案 15 :(得分:2)

var fs = require('fs');

function readfile(name,online,onend,encoding) {
    var bufsize = 1024;
    var buffer = new Buffer(bufsize);
    var bufread = 0;
    var fd = fs.openSync(name,'r');
    var position = 0;
    var eof = false;
    var data = "";
    var lines = 0;

    encoding = encoding || "utf8";

    function readbuf() {
        bufread = fs.readSync(fd,buffer,0,bufsize,position);
        position += bufread;
        eof = bufread ? false : true;
        data += buffer.toString(encoding,0,bufread);
    }

    function getLine() {
        var nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl && eof) return fs.closeSync(fd), online(data,++lines), onend(lines); 
        if (!hasnl && !eof) readbuf(), nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl) return process.nextTick(getLine);
        var line = data.substr(0,nl);
        data = data.substr(nl+1);
        if (data[0] === "\n") data = data.substr(1);
        online(line,++lines);
        process.nextTick(getLine);
    }
    getLine();
}

我遇到了同样的问题并提出了上述解决方案 看起来像其他人一样,但是是一个同步,可以很快读取大文件

希望这有帮助

答案 16 :(得分:2)

基于生成器的读卡器:https://github.com/neurosnap/gen-readlines

var fs = require('fs');
var readlines = require('gen-readlines');

fs.open('./file.txt', 'r', function(err, fd) {
  if (err) throw err;
  fs.fstat(fd, function(err, stats) {
    if (err) throw err;

    for (var line of readlines(fd, stats.size)) {
      console.log(line.toString());
    }

  });
});

答案 17 :(得分:2)

如果您想逐行读取文件并将其写入另一个文件:

var fs = require('fs');
var readline = require('readline');
var Stream = require('stream');

function readFileLineByLine(inputFile, outputFile) {

   var instream = fs.createReadStream(inputFile);
   var outstream = new Stream();
   outstream.readable = true;
   outstream.writable = true;

   var rl = readline.createInterface({
      input: instream,
      output: outstream,
      terminal: false
   });

   rl.on('line', function (line) {
        fs.appendFileSync(outputFile, line + '\n');
   });
};

答案 18 :(得分:1)

我有一个小模块可以很好地完成这项工作并被很多其他项目使用npm readline请注意,在节点v10中有一个原生readline模块,所以我将我的模块重新发布为linebyline https://www.npmjs.com/package/linebyline < / p>

如果您不想使用该模块,该功能非常简单:

var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
newlines = [
  13, // \r
  10  // \n
];
var readLine = module.exports = function(file, opts) {
if (!(this instanceof readLine)) return new readLine(file);

EventEmitter.call(this);
opts = opts || {};
var self = this,
  line = [],
  lineCount = 0,
  emit = function(line, count) {
    self.emit('line', new Buffer(line).toString(), count);
  };
  this.input = fs.createReadStream(file);
  this.input.on('open', function(fd) {
    self.emit('open', fd);
  })
  .on('data', function(data) {
   for (var i = 0; i < data.length; i++) {
    if (0 <= newlines.indexOf(data[i])) { // Newline char was found.
      lineCount++;
      if (line.length) emit(line, lineCount);
      line = []; // Empty buffer.
     } else {
      line.push(data[i]); // Buffer new line data.
     }
   }
 }).on('error', function(err) {
   self.emit('error', err);
 }).on('end', function() {
  // Emit last line if anything left over since EOF won't trigger it.
  if (line.length){
     lineCount++;
     emit(line, lineCount);
  }
  self.emit('end');
 }).on('close', function() {
   self.emit('close');
 });
};
util.inherits(readLine, EventEmitter);

答案 19 :(得分:1)

另一种解决方案是通过顺序执行器nsynjs运行逻辑。它使用节点readline模块逐行读取文件,并且它不使用promises或递归,因此不会在大文件上失败。以下是代码的外观:

var nsynjs = require('nsynjs');
var textFile = require('./wrappers/nodeReadline').textFile; // this file is part of nsynjs

function process(textFile) {

    var fh = new textFile();
    fh.open('path/to/file');
    var s;
    while (typeof(s = fh.readLine(nsynjsCtx).data) != 'undefined')
        console.log(s);
    fh.close();
}

var ctx = nsynjs.run(process,{},textFile,function () {
    console.log('done');
});

以上代码基于此示例:https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js

答案 20 :(得分:1)

2019年更新

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

这需要在您的计算机上安装最新的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();

答案 21 :(得分:1)

在进行此类操作时我们必须问自己两个问题:

  1. 用于执行此操作的内存量是多少?
  2. 内存消耗是否随着文件大小急剧增加?

诸如require('fs').readFileSync()之类的解决方案会将整个文件加载到内存中。这意味着执行操作所需的内存量几乎等于文件大小。对于任何大于50mbs

的东西,我们都应避免使用

通过在函数调用之后放置以下代码行,我们可以轻松跟踪函数的已用内存量

    const used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`
    );

目前,从大文件读取特定行的最佳方法是使用节点的readline。该文档具有惊人的examples

尽管我们不需要任何第三方模块。但是,如果您正在编写企业代码,则必须处理很多边缘情况。我不得不写一个非常轻量级的module called Apick File Storage来处理所有这些极端情况。

Apick文件存储模块:https://www.npmjs.com/package/apickfs 文档:https://github.com/apickjs/apickFS#readme

示例文件:https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

示例: 安装模块

npm i apickfs
// import module
const apickFileStorage = require('apickfs');
//invoke readByLineNumbers() method
apickFileStorage
  .readByLineNumbers(path.join(__dirname), 'big.txt', [163845])
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

此方法已成功测试了多达4 GB的密集文件。

big.text是具有163,845行的密集文本文件,大小为124 Mb。从该文件读取10行不同的脚本仅使用大约4.63 MB内存。并且它免费将有效的JSON解析为对象或数组。 ?太好了!

我们可以读取文件的一行或几百行,而只占用很少的内存。

答案 22 :(得分:1)

这是我最喜欢的处理文件的方式,这是一种用现代async/await读取渐进文件的简单本机解决方案(而不是“草率”或全内存方式)。在处理大型文本文件时,无需借助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);  // last line, if file does not end with newline

您可以在fs.createReadStream中调整编码或使用chunk.toString(<arg>)。另外,这还使您可以更好地根据您的口味微调分割线。使用.split(/\n+/)跳过空行并使用{ highWaterMark: <chunkSize> }控制块的大小。

别忘了创建类似processLine(line)的函数,以避免由于结尾buf剩余而重复两次行处理代码。不幸的是,ReadStream实例在此设置中不会更新其文件结束标志,因此afaik无法在循环中检测到我们是否在上次迭代中,而没有一些比较详细的技巧,例如比较fs.Stats().bytesRead中的文件大小。因此,这是最终的buf处理解决方案,除非您完全确定文件以换行符\n结尾,在这种情况下,for await循环就足够了。

★如果您更喜欢事件式异步版本,则为:

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) );

答案 23 :(得分:0)

我用这个:

function emitLines(stream, re){
    re = re && /\n/;
    var buffer = '';

    stream.on('data', stream_data);
    stream.on('end', stream_end);

    function stream_data(data){
        buffer += data;
        flush();
    }//stream_data

    function stream_end(){
        if(buffer) stream.emmit('line', buffer);
    }//stream_end


    function flush(){
        var re = /\n/;
        var match;
        while(match = re.exec(buffer)){
            var index = match.index + match[0].length;
            stream.emit('line', buffer.substring(0, index));
            buffer = buffer.substring(index);
            re.lastIndex = 0;
        }
    }//flush

}//emitLines

在流上使用此函数并侦听将要发出的行事件。

GR -

答案 24 :(得分:0)

虽然您应该使用instanceof模块,但最佳答案显示,interface QueryableServiceProvider { public function single(QueryStruct $parameters) : Model; } class ModelServiceProvider implements QueryableServiceProvider { public function single(QueryStruct $parameters): Model { //method body } } 似乎是面向命令行界面而不是行读取。它在缓冲方面也有点不透明。 (任何需要面向流式线路读卡器的人可能都想调整缓冲区大小)。 readline模块约为1000行,而统计和测试则为34行。

readline

这是一个更短的版本,没有统计数据,有19行:

readline

答案 25 :(得分:0)

const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, data) => {
var innerContent;
    console.log("Asynchronous read: " + data.toString());
    const lines = data.toString().split('\n')
    for (let line of lines)
        innerContent += line + '<br>';


});

答案 26 :(得分:0)

我将日常生产线处理的整个逻辑包装为npm模块:生产线套件 https://www.npmjs.com/package/line-kit

// example
var count = 0
require('line-kit')(require('fs').createReadStream('/etc/issue'),
                    (line) => { count++; },
                    () => {console.log(`seen ${count} lines`)})

答案 27 :(得分:-1)

我在下面的代码中使用了读取行,确认它不是一个目录,而且它不包含在文件列表中,不需要检查。

(function () {
  var fs = require('fs');
  var glob = require('glob-fs')();
  var path = require('path');
  var result = 0;
  var exclude = ['LICENSE',
    path.join('e2e', 'util', 'db-ca', 'someother-file'),
    path.join('src', 'favicon.ico')];
  var files = [];
  files = glob.readdirSync('**');

  var allFiles = [];

  var patternString = [
    'trade',
    'order',
    'market',
    'securities'
  ];

  files.map((file) => {
    try {
      if (!fs.lstatSync(file).isDirectory() && exclude.indexOf(file) === -1) {
        fs.readFileSync(file).toString().split(/\r?\n/).forEach(function(line){
          patternString.map((pattern) => {
            if (line.indexOf(pattern) !== -1) {
              console.log(file + ' contain `' + pattern + '` in in line "' + line +'";');
              result = 1;
            }
          });
        });
      }
    } catch (e) {
      console.log('Error:', e.stack);
    }
  });
  process.exit(result);

})();

答案 28 :(得分:-2)

我查看了以上所有答案,他们都使用第三方库来解决它。它在Node的API中有一个简单的解决方案。 e.g

const fs= require('fs')

let stream = fs.createReadStream('<filename>', { autoClose: true })

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))