node.js でファイル操作 (Streams)

今まで JavaScript ではファイルを扱うコードを書いたことはなかった(そもそも扱えないし)。node.js ではファイルも扱うことができる。今回は Stream インタフェースを試してみた。

% node -v
v0.5.0-pre

イベントの確認

まずは、イベントがどのような順番で起きるか確認してみる。入力ファイルの内容を pipe を使ってそのまま出力ファイルへ流し込む。

コード

input.txt

ABCDEFGHIJKLM
zyxwvutsrqpon
1234567890

streams1.js

var fs = require('fs');

var read  = fs.createReadStream('./input.txt', {bufferSize: 32});
var write = fs.createWriteStream('./output.txt');

read.on('data',  function (data)      { console.log('r: data');  })
    .on('end',   function ()          { console.log('r: end');   })
    .on('error', function (exception) { console.log('r: error'); })
    .on('close', function ()          { console.log('r: colse'); })
    .on('fd',    function (fd)        { console.log('r: fd');    });

write.on('drain', function ()         { console.log('w: drain'); })
     .on('error', function (exeption) { console.log('w: error'); })
     .on('close', function ()         { console.log('w: colse'); })
     .on('pipe',  function (src)      { console.log('w: pipe');  });

read.pipe(write);
実行
% node streams1.js 
w: pipe
r: data
w: drain
r: data
w: drain
r: end
w: colse
r: colse
% cat output.txt 
ABCDEFGHIJKLM
zyxwvutsrqpon
1234567890

出力ファイルに入力ファイルの内容がコピーされた。
エラーが発生しなかったので error イベントは発生していないのだろうけど、 fd イベントが起きないのは不明。マニュアルには、ファイルディスクリプタを受け取った時に起きると書かれているので、createReadStream() の完了時点で発生する?

1度に読み込むデータの確認

一度のイベントで読み込まれるデータはどういう単位か確認する。

コード

streams2.js

var fs = require('fs');

var read  = fs.createReadStream('./input.txt');

read.on('data', function (data) {
  console.log(data.length);
});
実行
% wc input.txt
 3  3 39 input.txt
% node streams2.js
39

改行などは関係なく、バッファサイズに収まるだけ読み込む。

エンコーディングを指定すると Buffer オブジェクトではなく、文字列としてデータが渡ってくるのでその場合はどうか確認する。

コード

streams3.js

var fs = require('fs');

var read  = fs.createReadStream('./input.txt', {encoding: 'utf8'});

read.on('data', function (data) {
  console.log(data.length);
});
実行
% node streams3.js
39

変わらず。ま、Buffer の場合と文字列の場合で挙動が変わるのはよろしくないので当然か。それに Stream だしね。改行などで途切れちゃうのはなんかイメージと違うし。
行単位でファイルを読み込み、処理をするような場合には向かない。

フィルター

入力ファイルの内容にフィルタをかけて出力してみる。大文字に変換するフィルタを作ってみた。

コード

streams4.js

var fs = require('fs');

var read  = fs.createReadStream('./input.txt');
var write = fs.createWriteStream('./output.txt');

read.on('data', function (data) {
  var upper_str = data.toString().toUpperCase();
  data.write(upper_str);
});

read.pipe(write);
実行
% node streams4.js 
% cat output.txt  
ABCDEFGHIJKLM
ZYXWVUTSRQPON
1234567890

read ストリームの data イベントで渡された Buffer オブジェクトの内容に対して大文字変換を行うようにした。引数が Buffer ではなく 文字列の場合は、内容を操作できるのだろうか。