Node.js
Streams verwenden
Suche…
Parameter
Parameter | Definition |
---|---|
Lesbarer Stream | Typ des Streams, aus dem Daten gelesen werden können |
Beschreibbarer Stream | Typ des Streams, in den Daten geschrieben werden können |
Duplex-Stream | Typ des Streams, der sowohl lesbar als auch schreibbar ist |
Stream umwandeln | Typ des Duplex-Streams, der Daten konvertieren kann, während sie gelesen und dann geschrieben werden |
Daten aus Textdatei mit Streams lesen
Die E / A im Knoten ist asynchron, sodass bei der Interaktion mit der Festplatte und dem Netzwerk Callbacks an Funktionen übergeben werden. Sie könnten versucht sein, Code zu schreiben, der eine Datei von der Festplatte abruft:
var http = require('http');
var fs = require('fs');
var server = http.createServer(function (req, res) {
fs.readFile(__dirname + '/data.txt', function (err, data) {
res.end(data);
});
});
server.listen(8000);
Dieser Code funktioniert, ist aber sperrig und puffert die gesamte Datei data.txt für jede Anforderung in den Speicher, bevor das Ergebnis an die Clients zurückgeschrieben wird. Wenn data.txt sehr groß ist, kann Ihr Programm viel Speicherplatz beanspruchen, da es gleichzeitig viele Benutzer bedient, insbesondere für Benutzer mit langsamen Verbindungen.
Die Benutzererfahrung ist ebenfalls schlecht, da Benutzer warten müssen, bis die gesamte Datei in den Speicher Ihres Servers gepuffert ist, bevor sie Inhalte empfangen können.
Glücklicherweise sind beide Argumente (req, res) Streams, was bedeutet, dass wir dies mit fs.createReadStream () anstelle von fs.readFile () viel besser schreiben können:
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);
});
server.listen(8000);
Hier sorgt .pipe () für das Abhören von 'data' und 'end' Ereignissen aus dem fs.createReadStream (). Dieser Code ist nicht nur sauberer, sondern die Datei "data.txt" wird jetzt für jeden einzelnen Block sofort in die Clients geschrieben, sobald sie von der Festplatte empfangen werden.
Piping-Streams
Lesbare Streams können zu "Pipes" oder zu schreibbaren Streams verbunden werden. Dadurch wird der Datenfluss vom Quellstream zum Zielstream ohne großen Aufwand durchgeführt.
var fs = require('fs')
var readable = fs.createReadStream('file1.txt')
var writable = fs.createWriteStream('file2.txt')
readable.pipe(writable) // returns writable
Wenn beschreibbare Streams auch lesbare Streams sind, dh wenn sie Duplex- Streams sind, können Sie sie an andere beschreibbare Streams weiterleiten.
var zlib = require('zlib')
fs.createReadStream('style.css')
.pipe(zlib.createGzip()) // The returned object, zlib.Gzip, is a duplex stream.
.pipe(fs.createWriteStream('style.css.gz')
Lesbare Streams können auch in mehrere Streams geleitet werden.
var readable = fs.createReadStream('source.css')
readable.pipe(zlib.createGzip()).pipe(fs.createWriteStream('output.css.gz'))
readable.pipe(fs.createWriteStream('output.css')
Beachten Sie, dass Sie synchron (gleichzeitig) an die Ausgabeströme weiterleiten müssen, bevor Daten "fließen". Andernfalls können unvollständige Daten gestreamt werden.
Beachten Sie auch, dass Stream-Objekte error
ausgeben können. Vergewissern Sie sich, dass Sie diese Ereignisse in jedem Stream nach Bedarf verantwortungsbewusst behandeln:
var readable = fs.createReadStream('file3.txt')
var writable = fs.createWriteStream('file4.txt')
readable.pipe(writable)
readable.on('error', console.error)
writable.on('error', console.error)
Erstellen Sie einen eigenen lesbaren / beschreibbaren Stream
Wir werden Stream-Objekte sehen, die von Modulen wie FS usw. zurückgegeben werden. Was aber, wenn wir unser eigenes Streamable-Objekt erstellen möchten?
Um ein Stream-Objekt zu erstellen, müssen Sie das von NodeJs bereitgestellte Stream-Modul verwenden
var fs = require("fs");
var stream = require("stream").Writable;
/*
* Implementing the write function in writable stream class.
* This is the function which will be used when other stream is piped into this
* writable stream.
*/
stream.prototype._write = function(chunk, data){
console.log(data);
}
var customStream = new stream();
fs.createReadStream("am1.js").pipe(customStream);
Dies gibt uns unseren eigenen benutzerdefinierten Stream. wir können alles innerhalb der _write- Funktion implementieren . Die obige Methode funktioniert in NodeJs 4.xx-Version, aber in NodeJs 6.x ES6 haben Klassen eingeführt, weshalb sich die Syntax geändert hat. Unten ist der Code für die 6.x-Version von NodeJs
const Writable = require('stream').Writable;
class MyWritable extends Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, callback) {
console.log(chunk);
}
}
Warum Streams?
Schauen wir uns die folgenden zwei Beispiele an, um den Inhalt einer Datei zu lesen:
Die erste, die eine asynchrone Methode zum Lesen einer Datei verwendet und eine Rückruffunktion bereitstellt, die aufgerufen wird, sobald die Datei vollständig in den Speicher eingelesen ist:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
Und die zweite, die streams
verwendet, um den Inhalt der Datei Stück für Stück zu lesen:
var fileStream = fs.createReadStream(`${__dirname}/file`);
var fileContent = '';
fileStream.on('data', data => {
fileContent += data.toString();
})
fileStream.on('end', () => {
console.log(fileContent);
})
fileStream.on('error', err => {
handleError(err)
})
Es ist erwähnenswert, dass beide Beispiele dasselbe tun . Was ist dann der Unterschied?
- Der erste ist kürzer und sieht eleganter aus
- Mit der zweiten können Sie die Datei bearbeiten, während sie gelesen wird (!).
Wenn die Dateien, mit denen Sie arbeiten, klein sind, gibt es keinen wirklichen Effekt bei der Verwendung von streams
, aber was passiert, wenn die Datei groß ist? (so groß, dass es 10 Sekunden dauert, um es in den Speicher zu lesen)
Ohne streams
Sie warten und absolut nichts tun (es sei denn, Ihr Prozess erledigt anderes), bis die 10 Sekunden vergangen sind und die Datei vollständig gelesen ist. Erst dann können Sie die Datei verarbeiten.
Mit streams
Sie den Inhalt der Datei Stück für Stück, sobald sie verfügbar sind. Dadurch können Sie die Datei verarbeiten, während sie gelesen wird.
Das obige Beispiel veranschaulicht nicht, wie streams
für Arbeiten verwendet werden können, die beim Callback-Modus nicht ausgeführt werden können. Schauen wir uns also ein anderes Beispiel an:
Ich möchte eine herunterladen gzip
- Datei, entpacken und seinen Inhalt auf die Festplatte speichern. In Anbetracht der url
der Datei müssen Sie url
tun:
- Laden Sie die Datei herunter
- Entpacke die Datei
- Speichern Sie es auf der Festplatte
Hier ist eine [kleine Datei] [1], die in meinem S3
Speicher gespeichert ist. Der folgende Code führt das oben auf die Rückrufweise aus.
var startTime = Date.now()
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}, (err, data) => {
// here, the whole file was downloaded
zlib.gunzip(data.Body, (err, data) => {
// here, the whole file was unzipped
fs.writeFile(`${__dirname}/tweets.json`, data, err => {
if (err) console.error(err)
// here, the whole file was written to disk
var endTime = Date.now()
console.log(`${endTime - startTime} milliseconds`) // 1339 milliseconds
})
})
})
// 1339 milliseconds
So sieht es mit streams
:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
Ja, bei kleinen Dateien geht es nicht schneller - die getestete Datei 80KB
. Das Testen einer größeren Datei mit 71MB
MB ( 382MB
MB entpackt) zeigt, dass die streams
Version viel schneller ist
- Es dauerte 20925 Millisekunden zum Download
71MB
, entpacken und dann schreiben382MB
auf der Festplatte - mit der Callback Mode. - Im Vergleich dazu dauerte es 13434 Millisekunden, um dasselbe bei Verwendung der
streams
Version zu tun (35% schneller, für eine nicht so große Datei).