关于javascript:在node.js中一次读取一行文件?

Read a file one line at a time in node.js?

我试图一次读一行大文件。我找到了一个关于Quora的问题,这个问题涉及到了这个问题,但我遗漏了一些联系,以使整个问题协调一致。

1
2
3
4
5
6
7
8
9
 var Lazy=require("lazy");
 new Lazy(process.stdin)
     .lines
     .forEach(
          function(line) {
              console.log(line.toString());
          }
 );
 process.stdin.resume();

我想知道的一点是如何从一个文件而不是本示例中的stdin中一次读取一行。

我试过:

1
2
3
4
5
6
 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核心模块。以下是从文件中读取行的最简单方法,无需任何外部模块:

1
2
3
4
5
6
7
var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream('file.in')
});

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

最后一行被正确读取(从节点v0.12或更高版本开始),即使没有最终的

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


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

1
2
3
4
5
6
7
8
9
10
11
12
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文件,而是必须创建一个ReadStream

fs.createReadStream

然后把那条河转到Lazy


有一个非常好的模块可以一行一行地读取文件,它被称为行阅读器。

用它你只需写:

1
2
3
4
5
6
7
8
9
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风格"界面重复文件:

1
2
3
4
5
6
7
lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});


1
2
3
4
5
require('fs').readFileSync('file.txt', 'utf-8').split(/
?
/).forEach(function(line){
  console.log(line);
})


老话题,但这很管用:

1
2
3
4
5
6
7
8
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
})

简单。不需要外部模块。


你可以随时滚动你自己的线阅读器。我还没有将此代码段作为基准,但它正确地将传入的块流拆分为行,而不带尾部''

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var last ="";

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

    lines = (last+chunk).split("
"
);
    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解决方案可能会很感兴趣。


对于载体模块:

1
2
3
4
5
6
var carrier = require('carrier');

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


由于节点工作中的drain/pause/resume方式(请参阅:http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/(我爱这家伙btw)),我最终在尝试处理这些行并将它们写入另一个流时使用了一行一行的懒惰读取内存泄漏。我还没有仔细观察lazy来理解原因,但是我不能暂停我的阅读流以允许在没有lazy退出的情况下进行消耗。

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

如果使用惰性行运行以前的修订,则会泄漏。最新的版本根本不会泄漏,您可能可以将其用作读卡器/处理器的基础。虽然我有一些定制的东西在里面。

编辑:我想我也应该注意,我的代码使用lazy很好地工作,直到我发现自己写了足够大的XML片段,这会耗尽/暂停/恢复,因为这是必要的。对于较小的块,它是好的。


编辑:

使用转换流。

使用BufferedReader,您可以读取行。

1
2
3
4
5
6
7
8
9
10
11
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 ();


由于没有一个全面的解决方案,我很沮丧,所以我把自己的尝试(git/npm)放在一起。复制粘贴的功能列表:

  • 交互式行处理(基于回调,不将整个文件加载到RAM中)
  • 或者,返回数组中的所有行(详细或原始模式)
  • 以交互方式中断流式处理,或执行类似映射/筛选器的处理
  • 检测任何换行约定(PC/Mac/Linux)
  • 正确的EOF/最后一行处理
  • 多字节UTF-8字符的正确处理
  • 按行检索字节偏移量和字节长度信息
  • 随机访问,使用基于行或基于字节的偏移量
  • 自动映射线偏移信息,加快随机访问速度
  • 零依赖项
  • 测验

美国国立卫生研究院?你决定:


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

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

没有在很大的文件上测试过。如果有,请通知我们。


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

1
2
3
4
5
6
7
8
9
const fs = require("fs")

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

  for (let line of lines)
    console.log(line)
});


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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){ //
 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("++++++++++++++++++++")
})


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

1
2
3
while (<>) {
    process_line($_);
}

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

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

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

经过令人惊讶的努力和几个错误的启动后,这就是我想到的代码。它非常快,但比我想象的要小:(在Github上分叉)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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('
'
, 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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());
    }

  });
});

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 + '
'
);
   });
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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("
"
), hasnl = nl !== -1;
        if (!hasnl && eof) return fs.closeSync(fd), online(data,++lines), onend(lines);
        if (!hasnl && !eof) readbuf(), nl = data.indexOf("
"
), hasnl = nl !== -1;
        if (!hasnl) return process.nextTick(getLine);
        var line = data.substr(0,nl);
        data = data.substr(nl+1);
        if (data[0] ==="
"
) data = data.substr(1);
        online(line,++lines);
        process.nextTick(getLine);
    }
    getLine();
}

我也有同样的问题,并提出了上述解决方案在其他人看来是模拟的,但是是异步的,可以很快地读取大文件。

希望这有帮助


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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


我有一个小模块,它做得很好,并被许多其他项目使用NPM readline注意到,在节点v10中有一个本地readline模块,所以我将我的模块重新发布为linebyline https://www.npmjs.com/package/linebyline

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
newlines = [
  13, //
  10  //

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

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

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


我用这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function emitLines(stream, re){
    re = re && /
/;
    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 = /
/;
        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行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const EventEmitter = require('events').EventEmitter;
class LineReader extends EventEmitter{
    constructor(f, delim='
'
){
        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行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class LineReader extends require('events').EventEmitter{
    constructor(f, delim='
'
){
        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);
        });
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
const fs = require("fs")

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


});


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
(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(/
?
/).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);

})();

我已经看了以上所有的答案,所有的答案都是使用第三方库来解决的。它在节点的API中有一个简单的解决方案。例如

1
2
3
4
5
6
7
const fs= require('fs')

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

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