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

| 我正在尝试一次读取一行大文件。我在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之类的东西,但是我想弄清楚这一点。 我认为其他答案不会起作用,因为该文件比我正在运行该文件的服务器要大得多。     
已邀请:
        从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);
});
即使没有最后的“ 3”,也可以正确读取最后一行(从Node v0.12或更高版本开始)。 更新:此示例已添加到Node \'s API官方文档中。     
        对于这样的简单操作,不应依赖第三方模块。放轻松。
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);
});
    
        您不必
open
文件,而是必须创建a6ѭ文件。
fs.createReadStream
然后将该流传递给
Lazy
    
        有一个非常好的模块用于逐行读取文件,称为行读取器 使用它,您只需编写:
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);
    });
  }
});
    
        
require(\'fs\').readFileSync(\'file.txt\', \'utf-8\').split(/\\r?\\n/).forEach(function(line){
  console.log(line);
})
    
        旧主题,但这可行:
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
})
简单。无需外部模块。     
        您可以随时滚动自己的线路阅读器。我尚未对该代码段进行基准测试,但是它正确地将传入的数据块流分割成几行,而没有尾随的\'\\ 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和node而不是使用perl或bash来进行此操作会很不错。 无论如何,我的确感到小型的nodejs脚本应该是自包含的,而不是依赖于第三方模块,因此在阅读了该问题的所有答案之后,每个脚本都使用各种模块来处理行解析,因此可能有兴趣使用13 SLOC本机nodejs解决方案。     
        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();
    
        使用载体模块:
var carrier = require(\'carrier\');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log(\'got one line: \' + line);
});
    
        由于节点中消耗/暂停/恢复的工作方式,我尝试使用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 如果您使用“懒行”运行以前的修订版,则会泄漏。最新版本完全不会泄漏,您可以将其用作读取器/处理器的基础。虽然我那里有一些定制的东西。 编辑:我想我还应该注意,在我发现自己编写了足够大的xml片段以进行必要的消耗/暂停/恢复操作之前,使用Lazy的代码运行良好。对于较小的块,这很好。     
        编辑: 使用转换流。 使用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 ();
    
        自从发布原始答案以来,我发现split是一个非常易于使用的节点模块,用于在文件中进行行读取。也接受可选参数。
var split = require(\'split\');
fs.createReadStream(file)
    .pipe(split())
    .on(\'data\', function (line) {
      //each chunk now is a seperate line! 
    });
尚未对大型文件进行测试。让我们知道您是否愿意。     
缺乏针对此问题的全面解决方案令我感到沮丧,因此我进行了自己的尝试(git / npm)。复制粘贴的功能列表: 交互式线路处理(基于回调,不将整个文件加载到RAM中) (可选)返回数组中的所有行(详细或原始模式) 交互式中断流,或执行类似地图/过滤器的处理 检测任何换行符约定(PC / Mac / Linux) 正确的eof /最后行处理 正确处理多字节UTF-8字符 逐行检索字节偏移和字节长度信息 随机访问,使用基于行或基于字节的偏移量 自动映射线偏移信息,以加快随机访问 零依赖 测验 NIH?你决定 :-)     
        
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(\"++++++++++++++++++++\")
})
    
        在大多数情况下,这应该足够了:
const fs = require(\"fs\")

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

  for (let line of lines)
    console.log(line)
});
    
        我想解决同样的问题,在Perl中基本上是这样的:
while (<>) {
    process_line($_);
}
我的用例只是一个独立的脚本,而不是服务器,因此同步很好。这些是我的标准: 可以在许多项目中重用的最小同步代码。 文件大小或行数没有限制。 线长没有限制。 能够处理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;
    }
  }
}
可能会进一步清除它,这是反复试验的结果。     
        基于生成器的行读取器: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());
    }

  });
});
    
        如果要逐行读取文件并将其写入另一个文件:
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\');
   });
};
    
        
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();
}
我有同样的问题,并提出了上述解决方案 看起来与其他人相似,但是是aSync,可以非常快速地读取大文件 希望这会有所帮助     
        我有一个能很好地完成此工作的模块,并由其他许多项目使用。npm readline注意在节点v10中,有一个本机的readline模块,所以我将模块重新发布为linebyline https://www.npmjs.com/package/逐行 如果您不想使用该模块,则功能非常简单:
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);
    
        另一个解决方案是通过顺序执行程序nsynjs运行逻辑。它使用节点readline模块逐行读取文件,并且不使用promise或递归,因此不会在大文件上失败。代码如下所示:
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     
        我用这个:
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-     
        尽管您可能应该使用
readline
模块作为最佳答案,但
readline
似乎是面向命令行界面而不是行读取。在缓冲方面也有些不透明。 (任何需要面向流线的阅读器的人都可能希望调整缓冲区大小)。 readline模块为〜1000行,而带有统计和测试的行为34。
const EventEmitter = require(\'events\').EventEmitter;
class LineReader extends EventEmitter{
    constructor(f, delim=\'\\n\'){
        super();
        this.totalChars = 0;
        this.totalLines = 0;
        this.leftover = \'\';

        f.on(\'data\', (chunk)=>{
            this.totalChars += chunk.length;
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) lines.pop();
            this.totalLines += lines.length;
            for (let l of lines) this.onLine(l);
        });
        // f.on(\'error\', ()=>{});
        f.on(\'end\', ()=>{console.log(\'chars\', this.totalChars, \'lines\', this.totalLines)});
    }
    onLine(l){
        this.emit(\'line\', l);
    }
}
//Command line test
const f = require(\'fs\').createReadStream(process.argv[2], \'utf8\');
const delim = process.argv[3];
const lineReader = new LineReader(f, delim);
lineReader.on(\'line\', (line)=> console.log(line));
这是一个更短的版本,没有统计信息,共有19行:
class LineReader extends require(\'events\').EventEmitter{
    constructor(f, delim=\'\\n\'){
        super();
        this.leftover = \'\';
        f.on(\'data\', (chunk)=>{
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) 
                lines.pop();
            for (let l of lines)
                this.emit(\'line\', l);
        });
    }
}
    
        
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>\';


});
    
        我已经仔细阅读了以上所有答案,所有的答案都使用第三方库来解决。它在Node \'s API中有一个简单的解决方案。例如
const fs= require(\'fs\')

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

stream.on(\'data\', chunk => {
    let row = chunk.toString(\'ascii\')
}))
    
        我将日常生产线处理的整个逻辑包装为npm模块:line-kit 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`)})
在执行此类操作时,我们必须问自己两个问题: 用于执行此操作的内存量是多少? 内存消耗是否随着文件大小而急剧增加? ѭ35之类的解决方案会将整个文件加载到内存中。这意味着执行操作所需的内存量将几乎等于文件大小。我们应该避免使用大于anything36的东西 通过在函数调用之后放置以下代码行,我们可以轻松跟踪函数使用的内存量:
    const used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`
    );
现在,从大文件读取特定行的最佳方法是使用节点的读取行。该文档有一个惊人的示例。 尽管我们不需要任何第三方模块。但是,如果您正在编写企业代码,则必须处理很多边缘情况。我必须编写一个名为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解析为对象或数组。

要回复问题请先登录注册