Node.js Stream モジュール

Java FileNode.js 組み込みモジュール


Stream(ストリーム)は、Node.js でストリーミングデータを処理するための抽象インターフェースです。ストリームを水流のように想像できます。データは水のように、ある場所から別の場所へ流れます。この処理方法は、大きなファイルや連続的なデータソースの処理に特に適しています。すべてのデータを一度にメモリに読み込む必要がないからです。

なぜ Stream が必要なのか?

  1. メモリ効率:大きなファイルを処理する場合、ファイル全体を一度にメモリに読み込む必要がない
  2. 時間効率:データを受け取りながら処理できるため、すべてのデータが準備できるまで待つ必要がない
  3. 合成性:複数のストリームをパイプのように接続して、データ処理パイプラインを形成できる

Stream の4つの基本タイプ

1. Readable Stream(読み取り可能ストリーム)

読み取り可能ストリームはデータの供給源であり、そこからデータを読み取ることができます。例えば、ファイルからコンテンツを読み取ったり、HTTP リクエストからデータを取得したりします。

例

const fs = require('fs');
const readableStream = fs.createReadStream('example.txt');

readableStream.on('data', (chunk) => {
  console.log(`受け取った ${chunk.length}バイトのデータ`);
});

readableStream.on('end', () => {
  console.log('これ以上のデータはありません');
});

2. Writable Stream(書き込み可能ストリーム)

書き込み可能ストリームはデータの宛先であり、そこにデータを書き込むことができます。例えば、ファイルに書き込んだり、HTTP レスポンスを送信したりします。

例

const fs = require('fs');
const writableStream = fs.createWriteStream('output.txt');

writableStream.write('1行目のデータ\n');
writableStream.write('2行目のデータ\n');
writableStream.end('最後の行のデータ');

3. Duplex Stream(双方向ストリーム)

双方向ストリームは読み取り可能であり、書き込み可能でもあります。例えば TCP ソケットです。

例

const { Duplex } = require('stream');

const myDuplex = new Duplex({
  write(chunk, encoding, callback) {
    console.log(chunk.toString());
    callback();
  },
 
  read(size) {
    this.push('双方向ストリームから読み取ったデータ');
    this.push(null); // データの終了を示す
  }
});

4. Transform Stream(変換ストリーム)

変換ストリームは特別な双方向ストリームであり、読み書きの過程でデータを変更または変換できます。例えば、データの圧縮や暗号化です。

例

const { Transform } = require('stream');

const upperCaseTr = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  }
});

process.stdin.pipe(upperCaseTr).pipe(process.stdout);

Stream の一般的なメソッド

pipe() メソッド

pipe()pipe() メソッドは Stream の最も強力な機能の1つであり、複数のストリームを接続してパイプラインを形成できます。

例

const fs = require('fs');

// 読み取りストリームと書き込みストリームを作成する
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('output.txt');

// pipe を使用して読み取りストリームと書き込みストリームを接続する
readStream.pipe(writeStream);

writeStream.on('finish', () => {
  console.log('データの書き込みが完了しました');
});

イベント監視

Stream は EventEmitter のインスタンスであり、イベントを監視することでさまざまな状況を処理できます。

  • data- 読み取り可能なデータがあるときに発生します
  • end- 読み取るデータがなくなったときに発生します
  • error- エラーが発生したときに発生します
  • finish- すべてのデータが基盤システムにフラッシュされたときに発生します

実際の応用例

例1:ファイルコピー

例

const fs = require('fs');

function copyFile(source, target, cb) {
  // 読み書きストリームを作成
  const rd = fs.createReadStream(source);
  const wr = fs.createWriteStream(target);

  // エラーを処理
  rd.on('error', err => cb(err));
  wr.on('error', err => cb(err));
 
  // 完了コールバック
  wr.on('finish', cb);
 
  // コピーを開始
  rd.pipe(wr);
}

// 使用
copyFile('source.txt', 'target.txt', err => {
  if (err) console.error(err);
  else console.log('コピーが完了しました');
});

例2:HTTP サーバーでのファイル圧縮

例

const fs = require('fs');
const http = require('http');
const zlib = require('zlib');

http.createServer((req, res) => {
  // 読み取りストリームを作成
  const readStream = fs.createReadStream('./largeFile.txt');
 
  // レスポンスヘッダーを設定
  res.writeHead(200, {
    'Content-Type': 'text/plain',
    'Content-Encoding': 'gzip'
  });
 
  // パイプライン:読み取り -> 圧縮 -> レスポンス
  readStream.pipe(zlib.createGzip()).pipe(res);
}).listen(3000);

Stream のベストプラクティス

  1. エラー処理:常にストリームにエラー処理リスナーを追加する
  2. メモリ管理:大きなファイルの場合は、一括読み込みではなくストリームを使用する
  3. パイプライン:pipe() メソッドを適切に使用して複数のストリームを組み合わせる
  4. 背圧処理:書き込み速度が読み取り速度に追いつかない場合、背圧を適切に処理する必要があります

エラー処理の例

例

const fs = require('fs');

const readStream = fs.createReadStream('not-exist-file.txt');

readStream.on('error', (err) => {
  console.error('エラーが発生しました:', err.message);
});

Node.js の Stream モジュールを習得することで、さまざまな I/O 操作を効率的に処理できます。特に大きなファイルやリアルタイムデータを扱う際に、ストリームは強力なツールとなるでしょう。

Java FileNode.js 組み込みモジュール

その他の拡張