Stream は、Node 内の多くのオブジェクトによって実装される抽象インターフェイスです。たとえば、HTTP サーバーへのリクエストはストリームであり、stdout もストリームです。ストリームは読み取り可能、書き込み可能、またはその両方です。
Stream への最初の取り組みは Unix の初期に始まり、数十年にわたる実践により、Stream のアイデアがいくつかの巨大なシステムを簡単に開発できることが証明されました。
Unix では、Stream は「|」によって実装されます。ノードでは、組み込みストリームモジュールとして、多くのコアモジュールとサードパーティモジュールが使用されます。
Unix と同様に、ノード ストリームの主な操作も .pipe() であり、ユーザーはアンチプレッシャー メカニズムを使用して読み取りと書き込みのバランスを制御できます。
Stream は、再利用可能な統合インターフェイスを開発者に提供し、抽象的な Stream インターフェイスを通じてストリーム間の読み取りと書き込みのバランスを制御できます。
TCP 接続は読み取り可能なストリームと書き込み可能なストリームの両方ですが、HTTP 要求オブジェクトは読み取り可能なストリームであり、http 応答オブジェクトは書き込み可能なストリームです。
ノードの I/O は非同期であるため、ディスクとネットワークへの読み取りと書き込みにはデータを読み取るためのコールバック関数が必要です。 以下はファイルのダウンロードの例です
。
上記のコード:
サイズが大きく、同時実行の量が多い場合、大量のメモリが浪費されます。ユーザーはファイル データを受け入れる前に、ファイル全体がメモリにキャッシュされるまで待つ必要があるため、結果は
になります。
ユーザーエクスペリエンスはかなり悪いです。幸いなことに、両方のパラメータ (req、res) は Stream なので、fs.readFile() の代わりに fs.createReadStream() を使用できます。以下のように:
var http = require('http') ;
var fs = require('fs') ;
var server = http.createServer(function (req, res) {
var stream = fs.createReadStream(__dirname '/data.txt') ;
Stream.pipe(res) ;
}) ;
サーバー.listen(8888) ;
.pipe() メソッドは fs.createReadStream() の 'data' および 'end' イベントをリッスンするため、「data.txt」ファイル全体をキャッシュする必要はありません
ファイルを使用すると、クライアント接続が完了した直後にデータ ブロックをクライアントに送信できます。 .pipe() を使用するもう 1 つの利点は、クライアントが
の場合に問題を解決できることです。
非常に長いエンドツーエンドの遅延によって引き起こされる読み取りと書き込みの不均衡の問題。
基本的なストリームには、読み取り可能、書き込み可能、変換、二重、および「クラシック」の 5 つがあります。 (具体的な使い方についてはAPIをご確認ください)
2.事例の紹介
処理する必要のあるデータを一度にメモリにロードできない場合、または読み取りと処理を同時に行う方が効率的である場合、データ ストリームを使用する必要があります。 NodeJS は、さまざまなストリームを通じてデータ ストリームに対する操作を提供します。
大きなファイルのコピー プログラムを例として、データ ソースの読み取り専用データ ストリームを作成できます。例は次のとおりです。
var rs = fs.createReadStream(パス名);
rs.on('データ', 関数 (チャンク) {
doSomething(chunk); // 自由に詳細を試してください
});
rs.on('end', function () {
cleanUp() ;
}) ;
コード内のデータ イベントは、doSomething 関数が処理できるかどうかに関係なく、継続的にトリガーされます。この問題を解決するために、コードを次のように引き続き変更できます。
var rs = fs.createReadStream(src) ;
rs.on('データ', 関数 (チャンク) {
rs.pause() ;
doSomething(chunk, function () {
rs.resume() ;
}) ;
}) ;
rs.on('end', function () {
cleanUp();
}) ;
doSomething 関数にコールバックが追加されたため、データを処理する前にデータの読み取りを一時停止し、データの処理後にデータの読み取りを続行できます。
さらに、次のように、データ ターゲットの書き込み専用データ ストリームを作成することもできます。
var rs = fs.createReadStream(src) ;
var ws = fs.createWriteStream(dst) ;
rs.on('データ', 関数 (チャンク) {
ws.write(chunk);
}) ;
rs.on('end', function () {
ws.end();
}) ;
doSomething が書き込み専用データ ストリームにデータを書き込むことで置き換えられた後、上記のコードはファイル コピー プログラムのように見えます。ただし、上記のコードには、書き込み速度が読み取り速度に追いつかない場合、書き込み専用データ ストリーム内のキャッシュがバーストしてしまうという問題があります。 .write メソッドの戻り値を使用して、受信データがターゲットに書き込まれるかキャッシュに一時的に配置されるかを判断できます。また、ドレイン イベントに基づいて、書き込み専用データ ストリームがいつデータを書き込んだかを判断できます。キャッシュ内でターゲットに書き込むと、次に書き込まれるデータを渡すことができます。コードは次のようになります:
var rs = fs.createReadStream(src) ;
var ws = fs.createWriteStream(dst) ;
rs.on('データ', 関数 (チャンク) {
If (ws.write(chunk) === false) {
rs.pause() ;
}
}) ;
rs.on('end', function () {
ws.end();
});
ws.on('ドレイン', function () {
rs.resume();
}) ;
ついに読み取り専用データフローから書き込み専用データフローへのデータ転送を実現し、防爆倉庫制御を組み込みました。上記の大きなファイルのコピー プログラムなど、これには多くの使用シナリオがあるため、NodeJS はこれを行うための .pipe メソッドを直接提供しており、その内部実装は上記のコードと似ています。
ファイルをコピーするより完全なプロセスは次のとおりです:
コードをコピー コードは次のとおりです:
var fs = require('fs'),
パス = require('パス'),
out = process.stdout;
var filePath = '/bb/bigbear.mkv';
var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('file.mkv');
var stat = fs.statSync(filePath);
var totalSize = stat.size;
var allowedLength = 0;
var lastSize = 0;
var startTime = Date.now();
readStream.on('data', function(chunk) {
渡された長さ = chunk.length;
if (writeStream.write(chunk) === false) {
readStream.pause();
}
});
readStream.on('end', function() {
writeStream.end();
});
writeStream.on('ドレイン', function() {
readStream.resume();
});
setTimeout(function show() {
var パーセント = Math.ceil((passedLength / totalSize) * 100);
var size = Math.ceil(passedLength / 1000000);
var diff = サイズ - lastSize;
lastSize = サイズ;
out.clearLine();
out.cursorTo(0);
out.write('すでに完了' サイズ 'MB, ' パーセント '%, 速度:' diff * 2 'MB/s');
if (passedLength
setTimeout(show, 500);
} else {
var endTime = Date.now();
console.log();
console.log('共用時間:' (endTime - startTime) / 1000 '秒。');
}
}, 500);
上の日付コードを "copy.js" として保存することができます。私は setTimeout を追加して (または setInterval を直接使用して)、
500 ミリ秒ごとに完了を観察し、完了したサイズ、百分率、および圧縮速度を制御台に書き込み、圧縮が完了すると総所要時間を計算します。
三、总结一下
(1)、ストリームの概念を理解します。
(2)、熟练は関連StreamのAPIを使用します
(3)、ブロックの制御に注意してください。例: サイズファイルの一部は、「チャンクデータ」の形式で分割処理を実行します。
(4)、パイプの使用
(5)、次の概念:TCP 接続は可読ストリームであり、また可写流でもありますが、Http 接続とは異なり、http 要求オブジェクトは可読ストリームであり、http 応答オブジェクトは可写流です