Node.js は本質的に非同期でイベント駆動型であるため、I/O 関連タスクの処理に最適です。アプリケーションで I/O 関連の操作を処理している場合は、Node.js のストリームを利用できます。それでは、ストリームを詳しく見て、ストリームがどのように I/O 操作を簡素化するかを理解しましょう。
ストリームとは
ストリームは、データ ソースからデータを簡単に読み取り、別の宛先にストリーミングできるようにする UNIX パイプです。
簡単に言えば、ストリームは特別なものではなく、いくつかのメソッドを実装する単なる EventEmitter です。実装方法に応じて、ストリームは読み取り可能なストリーム (Readable)、書き込み可能なストリーム (Writable)、または双方向ストリーム (Duplex、同時に読み取りと書き込みが可能) になります。
読み取り可能なストリームを使用するとデータ ソースからデータを読み取ることができ、書き込み可能なストリームを使用すると宛先にデータを書き込むことができます。
Node.js を使用したことがある場合は、おそらくフローに遭遇したことがあるでしょう。
たとえば、Node.js HTTP サーバーでは、リクエストは読み取り可能なストリームであり、応答は書き込み可能なストリームです。
読み取り可能および書き込み可能なストリームの処理に役立つ fs モジュールも使用したことがあるかもしれません。
次に、いくつかの基本を学び、さまざまな種類のストリームを理解しましょう。この記事では、読み取り可能なストリームと書き込み可能なストリームについて説明します。双方向ストリームについてはこの記事の範囲外であるため、説明しません。
読み取り可能なストリーム
読み取り可能なストリームを使用して、データ ソースからデータを読み取ることができます。このデータ ソースには、システム内のファイル、メモリ内のバッファ、または他のストリームなど、あらゆるものを使用できます。ストリームは EventEmitter であるため、さまざまなイベントとともにデータを送信します。これらのイベントを使用してフローを機能させます。
ストリームからデータを読み取る
ストリームからデータを読み取る最良の方法は、データ イベントをリッスンしてコールバック関数を追加することです。データが流入すると、読み取り可能なストリームがデータ イベントを送信し、コールバック関数がトリガーされます。次のコード スニペットを見てください:
var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; var readableStream.on('data', function(chunk){ data += chunk; }); readableStream.on('end', function(){ console.log(data); });
fs.createReadStream は読み取り可能なストリームを提供します。
当初、この流れはダイナミックではありませんでした。データのイベント リスナーを追加し、コールバック関数を追加すると、流動的になります。その後、小さなデータを読み取り、コールバック関数に渡します。
ストリームの実装者は、データ イベントのトリガー頻度を決定します。たとえば、HTTP リクエストは、数 KB のデータが読み取られたときにデータ イベントをトリガーします。 ファイルからデータを読み取る場合、行の読み取り時にデータ イベントを発生させることができます。
読み取るデータがない場合(ファイルの終わりが読み取られた場合)、ストリームは終了イベントを送信します。上の例では、このイベントをリッスンし、ファイルの読み取りが完了したときにデータを出力しました。
ストリームを読み取る別の方法もあります。ファイルの終わりを読み取る前に、ストリーム インスタンスで read() メソッドを呼び出し続ける必要があります。
var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; var chunk; readableStream.on('readable', function(){ while ((chunk = readableStream.read()) != null) { data += chunk; } }); readableStream.on('end', function(){ console.log(data); });
read() メソッドは、内部バッファからデータを読み取り、読み取るデータがない場合は null を返します。
そのため、while ループで read() が null を返すかどうかを確認し、null が返された場合はループを終了します。
ストリームからデータを読み取ることができると、readable イベントがトリガーされることに注意してください。
エンコーディングを設定する
デフォルトでは、ストリームから読み取るものは Buffer オブジェクトです。文字列を読みたい場合には、これは適していません。したがって、次の例のように Readable.setEncoding() を呼び出すことでストリームのエンコーディングを設定できます:
var fs = require('fs'); var readableStream = fs.createReadStream('file.txt'); var data = ''; readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk){ data += chunk; }); readableStream.on('end', function(){ console.log(data); });
上の例では、ストリームのエンコーディングを utf8 に設定し、データは utf8 に解析されます。コールバック関数内のチャンクは文字列になります。
Piping
Piping は、ストリームの状態を自分で管理することなく、データ ソースからデータを読み取り、宛先に書き込むことができる優れたメカニズムです。まず次の例を見てみましょう:
var fs = require('fs'); var readableStream = fs.createReadStream('file1.txt'); var writableStream = fs.createWriteStream('file2.txt'); readableStream.pipe(writableStream);
上の例では、pipe() メソッドを使用して file1 の内容を file2 に書き込みます。 Pipe() がデータ フローを管理するため、データ フローの速度を心配する必要はありません。これにより、pipe() が非常に簡潔になり、使いやすくなります。
pipe() は宛先ストリームを返すため、複数のストリームを簡単にリンクできることに注意してください。
チェーン
アーカイブ ファイルがあり、それを解凍したいとします。このタスクを達成するには多くの方法があります。しかし、最もクリーンな方法は、パイプとリンクを利用することです:
var fs = require('fs'); var zlib = require('zlib'); fs.createReadStream('input.txt.gz') .pipe(zlib.createGunzip()) .pipe(fs.createWriteStream('output.txt'));
まず、input.txt.gz 経由で読み取り可能なストリームを作成し、それからコンテンツを解凍する zlib.createGunzip() をストリームさせます。最後に、解凍されたコンテンツを別のファイルに書き込むための書き込み可能なストリームを追加します。
その他のメソッド
読み取り可能なストリームのいくつかの重要な概念について説明しましたが、知っておく必要があるメソッドをいくつか紹介します:
1.Readable.pause() – このメソッドはストリームのフローを一時停止します。つまり、データ イベントはトリガーされなくなります。
2.Readable.resume() – このメソッドは上記の逆で、一時停止したストリームを再開します。
3.Readable.unpipe() – このメソッドは宛先を削除します。パラメータが渡された場合は、特定の宛先からの読み取り可能なストリームが停止され、パラメータが渡されなかった場合は、すべての宛先が削除されます。
可写流 (Writable Streams)
可写流让你把数据写入目的地。就像可读流那样,这些也是 EventEmitter ,它们也会触发不同的事件。我们来看看可写流中会触发的事件和方法吧。
写入流
要把数据写如到可写流中,你需要在可写流实例中调用 write() 方法,看看下面的例子:
var fs = require('fs'); var readableStream = fs.createReadStream('file1.txt'); var writableStream = fs.createWriteStream('file2.txt'); readableStream.setEncoding('utf8'); readableStream.on('data', function(chunk){ writableStream.write('chunk'); });
上面的代码非常简单,它只是从输入流中读取数据,然后用 write() 写入到目的地中。
这个方法返回一个布尔值来表示写入是否成功。如果返回的是 true 那表示写入成功,你可以继续写入更多的数据。 如果是 false ,那意味着发生了什么错误,你现在不能继续写入了。可写流会触发一个 drain 事件来告诉你你可以继续写入数据。
写完数据后
当你不需要在写入数据的时候,你可以调用 end() 方法来告诉流你已经完成写入了。假设 res 是一个 HTTP response 对象,你通常会发送响应给浏览器:
res.write('Some Data!!');
res.end();
当 end() 被调用时,所有数据会被写入,然后流会触发一个 finish 事件。注意在调用 end() 之后,你就不能再往可写流中写入数据了。例如下面的代码就会报错:
res.write('Some Data!!');
res.end();
res.write('Trying to write again'); //Error !
这里有一些和可写流相关的重要事件:
1.error – 在写入或链接发生错误时触发
2.pipe – 当可读流链接到可写流时,这个事件会触发
3.unpipe – 在可读流调用 unpipe 时会触发
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持PHP中文网。
更多Node.js Streams文件读写操作详解相关文章请关注PHP中文网!