web-dev-qa-db-ja.com

node.jsのfs.createReadStreamとfs.readFileの長所と短所は何ですか?

Node.jsをいじっていますが、ファイルが存在することを確認し、writeHeadを使用して適切なMIMEタイプを送信したら、ファイルを読み取り、ネットワークに送信する2つの方法を発見しました。

// read the entire file into memory and then spit it out

fs.readFile(filename, function(err, data){
  if (err) throw err;
  response.write(data, 'utf8');
  response.end();
});

// read and pass the file as a stream of chunks

fs.createReadStream(filename, {
  'flags': 'r',
  'encoding': 'binary',
  'mode': 0666,
  'bufferSize': 4 * 1024
}).addListener( "data", function(chunk) {
  response.write(chunk, 'binary');
}).addListener( "close",function() {
  response.end();
});

問題のファイルがビデオのように大きなものである場合、fs.createReadStreamがより良いユーザーエクスペリエンスを提供する可能性があると仮定するのは正しいですか?ブロック的ではないように感じます。これは本当ですか?知っておく必要のある他の長所、短所、警告、または落とし穴はありますか?

71
Kent Brewster

"data"を "write()"に、 "close"を "end()"に接続する場合のより良いアプローチ:

_// 0.3.x style
fs.createReadStream(filename, {
  'bufferSize': 4 * 1024
}).pipe(response)

// 0.2.x style
sys.pump(fs.createReadStream(filename, {
  'bufferSize': 4 * 1024
}), response)
_

read.pipe(write)またはsys.pump(read, write)アプローチには、フロー制御も追加するという利点があります。そのため、書き込みストリームがデータをすぐに受け入れられない場合、読み取りストリームにバックオフするように指示し、メモリにバッファリングされるデータの量を最小限に抑えます。

_flags:"r"_および_mode:0666_は、それがFileReadStreamであるという事実によって暗示されています。 binaryエンコーディングは非推奨です。エンコーディングが指定されていない場合は、生データバッファでのみ機能します。

また、ファイルをより滑らかなものにする他の機能を追加することもできます。

  1. _req.headers.range_をスニッフィングし、/bytes=([0-9]+)-([0-9]+)/などの文字列と一致するかどうかを確認します。その場合は、その開始位置から終了位置までストリーミングするだけです。 (番号の欠落は0または「終了」を意味します。)
  2. Stat()呼び出しからのinodeと作成時間をETagヘッダーにハッシュします。 「if-none-match」がそのヘッダーに一致するリクエストヘッダーを取得した場合は、_304 Not Modified_を送り返します。
  3. Statオブジェクトのmtime日付に対して_if-modified-since_ヘッダーを確認します。指定された日付以降に変更されていない場合は304。

また、一般に、可能であれば、_Content-Length_ヘッダーを送信します。 (ファイルをstat- ingしているので、これが必要です。)

57
isaacs

fs.readFileは、指摘したとおりにファイル全体をメモリにロードしますが、fs.createReadStreamは指定したサイズのチャンクでファイルを読み取ります。

また、fs.createReadStreamがファイル全体を読み取り、クライアントへの送信を開始するだけであるのに対して、クライアントは読み取り中にチャンクで送信されるので、fs.readFileを使用してデータの受信をより速く開始します。これは無視できますが、ファイルが非常に大きく、ディスクが遅い場合は違いが生じる可能性があります。

ただし、これら2つの関数を100MBファイルで実行する場合、最初の関数は100MBのメモリを使用してファイルをロードしますが、後者は最大で4KBしか使用しないため、これについて考えてください。

編集:fs.readFileを使用する理由は特にありません。特に、大きなファイルを開くと言ったからです。

38

大きなファイルの場合、「readFile」はメモリ内のすべてのファイルコンテンツをバッファリングするためメモリを独占し、システムをハングさせる可能性があります。 ReadStreamはチャンクで読み取ります。

このコードを実行し、タスクマネージャーの[パフォーマンス]タブでメモリ使用量を確認します。

 var fs = require('fs');

const file = fs.createWriteStream('./big_file');


for(let i=0; i<= 1000000000; i++) {
  file.write('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n');
}

file.end();


//..............
fs.readFile('./big_file', (err, data) => {
  if (err) throw err;
  console.log("done !!");
});

実際、「完了!!」は表示されません。メッセージ。 「readFile」は、バッファがファイルコンテンツを保持するのに十分な大きさではないため、ファイルコンテンツを読み取ることができません。

「readFile」の代わりに、readStreamを使用してメモリ使用量を監視します。

注:コードは、Samer buna Node Pluralsightのコースから取得されます。

3
Deen John

別の、おそらくそれほどよく知られていないことは、Nodeはfs.readFile に比べ fs.createReadStream。これをテストして、何が最適かを確認する必要があります。また、Nodeのすべての新しいバージョンで、これが改善されていることを知っています(つまり、ガベージコレクターはこれらのタイプの状況でよりスマートになりました)。