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

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

更新 :这个例子已被添加到Node的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该文件,而是必须创建一个ReadStream

fs.createReadStream

然后将该流传递给Lazy

有一个非常好的模块来逐行读取文件,这就是所谓的线阅读器

https://github.com/nickewing/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-style”接口迭代文件:

 lineReader.open('file.txt', function(reader) { if (reader.hasNextLine()) { reader.nextLine(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解决方案可能是感兴趣的。

使用载体模块 :

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

最后,我使用Lazy逐行读取大量内存泄漏,尝试处理这些行并将它们写入另一个流,原因是节点中的drain / pause / resume方法(请参阅: http:// elegantcode ( /我喜欢这个人顺便说一句)(#)(#) / 2011/04/06 /采取婴儿步骤与节点js泵浦数据之间流/ 我没有仔细看懒惰,以便明白为什么,但是我不能暂停我的阅读流,以便在没有懒惰退出的情况下排水。

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

如果使用Lazy线运行以前的修订版,则会发生泄漏。 最新版本根本不会泄漏,您可以将其用作读取器/处理器的基础。 虽然我有一些自定义的东西在那里。

编辑:我想我也应该注意到我的代码与懒惰工作正常,直到我发现自己编写足够大的XML片段,因为必要性流失/暂停/恢复。 对于较小的块很好。

编辑:

使用变换流 。


用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 (); 
 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("++++++++++++++++++++") }) 
 require('fs').readFileSync('file.txt').toString().split(/\r?\n/).forEach(function(line){ console.log(line); }) 

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

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

NIH? 你决定 :-)

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

 var split = require('split'); fs.createReadStream(file) .pipe(split()) .on('data', function (line) { //each chunk now is a seperate 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(); } 

我有同样的问题,想出了上面的解决方案看起来与其他人相似,但是是一个同步,并可以很快阅读大文件

希望这有助于

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

 const fs = require("fs") fs.readFile('./file', 'utf-8', (err, file) => { const lines = file.split('\n') for (let line of lines) console.log(line) }); 

我有一个很好的模块,用于其他项目npm readline注意在节点v10中有一个本地readline模块,所以我重新发布我的模块作为linebyline https://www.npmjs.com/package/ linebyline

如果你不想使用该模块的功能是非常简单的:

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

另一个解决方案是通过顺序执行程序nsynjs运行逻辑。 它使用node 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); }); } } 

在验证其不是包含在文件列表中的目录及其不需要检查的文件之后,我在下面的代码中使用了读取行。

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