node.jsで行処理

追記:今はこちらのライブラリがおすすめ

www.npmjs.com


node.jsにはテキストを読み込んで行毎に処理できるライブラリがないので、自前で行処理を行う必要がある。

何も考えてないバージョン

何も考えなければ、難しくない。

var fs = require('fs');
var rs = fs.createReadStream("a_big_text_file.txt");
var data = "";
rs.on("data", function(chunk) {
  data += chunk;
});
rs.on("end", function() {
  var lines = data.split('\n');
  lines.forEach(function(line) {
    console.log(line);
  }) 
});

しかし、これはイケてない実装。使い捨てスクリプトで使うのはいいが、まちがってもサーバアプリに組み込んではいけない。

イケてない点は、まず、ファイルサイズと同じサイズのメモリが必要になる事。また、読み込み完了後、イベントループを糞詰まりさせてしまう。数万行あった場合、splitで切った配列のlengthも数万になる、等…

イベント駆動な実装

chunkを読み込みつつ、EventEmitterを使って改行コードを見つける毎にイベントを発生させる。

// line_reader.js

var util = require("util");
var events = require("events");

function LineReader(stream) {
    var self = this;
    this.buf = "";
    this.rs = stream || process.stdin;
    this.rs.on("data", function(chunk) { self._onStreamData(chunk) });
    this.rs.on("end", function() { self._onStreamEnd() });
    this.rs.on("close", function() { self._onStreamClose() });
    this.rs.setEncoding("utf8");
}
util.inherits(LineReader, events.EventEmitter);

LineReader.prototype.read = function() {
    this.rs.resume();
};

LineReader.prototype.destroy = function() {
    this.buf = "";
    this.rs.destroy();
};

LineReader.prototype._onStreamEnd = function() {
    if (this.buf) {
        this.emit('line', this.buf);
    }
    this.emit('end');
};

LineReader.prototype._onStreamClose = function() {
    this.emit('close');
};

LineReader.prototype._onStreamData = function(chunk) {
    
    this.rs.pause();
    this.buf += chunk;
    
    var self = this;
    (function searchLine() {
        var i = self.buf.indexOf('\n');
        if (i >= 0) {
            var line = self.buf.slice(0, i + 1);
            self.buf = self.buf.slice(i + 1);
            self.emit('line', line);
            process.nextTick(searchLine);
        } else {
            self.rs.resume();
        }
    })();
};

exports.LineReader = LineReader;
var fs = require("fs");
var LineReader = require('./line_reader.js').LineReader;

var rs = fs.createReadStream("a_big_text_file.txt");

var reader = new LineReader(rs);
reader.on("line", function(line){ process.stdout.write("> " + line) });
reader.read();