Was sind die Vor- und Nachteile von fs.createReadStream gegenüber fs.readFile in node.js?

74

Ich mache mit node.js rum und habe zwei Möglichkeiten entdeckt, eine Datei zu lesen und über das Kabel zu senden, sobald ich festgestellt habe, dass sie existiert, und den richtigen MIME-Typ mit writeHead gesendet habe:

// 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();
});

Bin ich zu Recht davon ausgegangen, dass fs.createReadStream eine bessere Benutzererfahrung bietet, wenn die betreffende Datei etwas Großes wie ein Video ist? Es fühlt sich an, als wäre es weniger blockartig; Ist das wahr? Gibt es andere Vor-, Nachteile, Vorbehalte oder Fallstricke, die ich wissen muss?

Kent Brewster
quelle

Antworten:

59

Ein besserer Ansatz, wenn Sie nur "Daten" an "write ()" und "close" an "end ()" anschließen möchten:

// 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)

Der Ansatz read.pipe(write)oder sys.pump(read, write)hat den Vorteil, dass auch eine Flusskontrolle hinzugefügt wird. Wenn der Schreibstrom Daten nicht so schnell akzeptieren kann, weist er den Lesestream an, sich zurückzuziehen, um die Datenmenge zu minimieren, die im Speicher gepuffert wird.

Die flags:"r"und mode:0666werden durch die Tatsache impliziert, dass es sich um eine handelt FileReadStream. Die binaryCodierung ist veraltet. Wenn keine Codierung angegeben ist, funktioniert sie nur mit den Rohdatenpuffern.

Sie können auch einige andere Extras hinzufügen, die Ihre Datei viel schlanker machen:

  1. Schnüffeln Sie nach req.headers.rangeund sehen Sie, ob es zu einer Saite wie passt /bytes=([0-9]+)-([0-9]+)/. In diesem Fall möchten Sie nur von diesem Start- bis Endstandort streamen. (Fehlende Zahl bedeutet 0 oder "das Ende".)
  2. Hash der Inode und der Erstellungszeit vom stat () -Aufruf in einen ETag-Header. Wenn Sie einen Anforderungsheader erhalten, dessen "if-none-match" mit diesem Header übereinstimmt, senden Sie a zurück 304 Not Modified.
  3. Überprüfen Sie die if-modified-sinceKopfzeile mtimeanhand des Datums auf dem Statistikobjekt. 304, wenn es seit dem angegebenen Datum nicht geändert wurde.

Wenn Sie können, senden Sie im Allgemeinen auch einen Content-LengthHeader. (Sie sind in statder Datei, also sollten Sie diese haben.)

isaacs
quelle
@isaacs, könnten Sie bitte ein Beispiel geben, wie diese 3 Schritte implementiert werden könnten, danke!
Eugene Kuzmenko
1
Die bufferSizeOption wurde zugunsten von abgelehnt highWaterMark.
Umair Ishaq
3
Wie beantwortet dies überhaupt die ursprünglich gestellte Frage?
CapturedTree
45

fs.readFilelädt die gesamte Datei in den Speicher, wie Sie bereits erwähnt haben, während fs.createReadStreamdie Datei in Blöcken der von Ihnen angegebenen Größe gelesen wird.

Der Client empfängt Daten auch schneller, fs.createReadStreamwenn sie beim fs.readFileLesen in Blöcken gesendet werden, während die gesamte Datei ausgelesen wird und erst dann an den Client gesendet wird . Dies kann vernachlässigbar sein, kann jedoch einen Unterschied machen, wenn die Datei sehr groß und die Festplatten langsam sind.

Denken Sie jedoch daran, wenn Sie diese beiden Funktionen in einer 100-MB-Datei ausführen, verwendet die erste 100 MB Speicher zum Laden der Datei, während die letztere höchstens 4 KB verwendet.

Bearbeiten: Ich sehe wirklich keinen Grund, warum Sie fs.readFilebesonders verwenden würden, da Sie sagten, dass Sie große Dateien öffnen werden.

Christian Joudrey
quelle
Das heißt, mit können fs.readFilewir den Fortschritt pro Beispiel nicht erfassen?
Elemento0
4

Wenn es sich um eine große Datei handelt, wird "readFile" den Speicher belasten, da der gesamte Dateiinhalt im Speicher gepuffert wird und Ihr System möglicherweise hängen bleibt. Während ReadStream Chunks einliest.

Führen Sie diesen Code aus und beobachten Sie die Speichernutzung auf der Registerkarte Leistung des Task-Managers.

 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 !!");
});

Tatsächlich sehen Sie nicht "erledigt !!" Botschaft. "readFile" kann den Dateiinhalt nicht lesen, da der Puffer nicht groß genug ist, um den Dateiinhalt aufzunehmen.

Verwenden Sie jetzt anstelle von "readFile" readStream und überwachen Sie die Speichernutzung.

Hinweis: Der Code stammt aus dem Samer Buna Node-Kurs auf Pluralsight

Deen John
quelle
0

Ein anderes, vielleicht nicht so bekannt , was, ist , dass ich glaube , dass Knoten ist besser bei Reinigung nicht verwendete Speicher nach der Verwendung im fs.readFileVergleich zu fs.createReadStream. Sie sollten dies testen, um zu überprüfen, was am besten funktioniert. Ich weiß auch, dass dies mit jeder neuen Version von Node besser geworden ist (dh der Garbage Collector ist mit solchen Situationen schlauer geworden).

carl-johan.blomqvist
quelle