我试图读取一个大文件一行在一次。我在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之类的东西,但我想弄清楚这个问题。

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


当前回答

虽然您可能应该像上面的答案所建议的那样使用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);
        });
    }
}

其他回答

我把日常行处理的整个逻辑包装成一个npm模块:line-kit https://www.npmjs.com/package/line-kit

/ /实例 Var计数= 0 需要(“line-kit”)(需要(fs) .createReadStream (/ etc /问题), (line) => {count++;}, () => {console.log(' seen ${count} lines ')})

你可以自己卷行读卡器。我还没有对这段代码进行基准测试,但它正确地将传入的块流分割成没有结尾的'\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 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
})

简单。不需要外部模块。

带载波模块:

var carrier = require('carrier');

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

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

执行它需要多少内存? 内存消耗是否随着文件大小的增加而急剧增加?

require('fs'). readfilesync()等解决方案将整个文件加载到内存中。这意味着执行操作所需的内存量将几乎等同于文件大小。对于大于50mbs的数据,我们应该避免使用这种方法

通过在函数调用之后放置以下代码行,我们可以很容易地跟踪函数所使用的内存量:

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

现在,从大文件中读取特定行最好的方法是使用node的readline。文档中有一些惊人的例子。