Node.js
Använda strömmar
Sök…
parametrar
Parameter | Definition |
---|---|
Läsbar ström | typ av ström där data kan läsas från |
Skrivbar ström | typ av ström där data kan skrivas till |
Duplexström | typ av ström som är både läsbar och skrivbar |
Transform Stream | typ av duplexström som kan omvandla data när de läses och sedan skrivs |
Läs data från TextFile med strömmar
I / O i noden är asynkron, så att interagera med disken och nätverket innebär att vidarebefordra återuppringningar till funktioner. Du kanske frestas att skriva kod som serverar en fil från en sådan skiva:
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);
Den här koden fungerar men den är skrymmande och buffrar upp hela data.txt-filen i minnet för varje begäran innan du skriver tillbaka resultatet till klienter. Om data.txt är mycket stort kan ditt program börja äta mycket minne eftersom det tjänar många användare samtidigt, särskilt för användare med långsamma anslutningar.
Användarupplevelsen är också dålig eftersom användare måste vänta på att hela filen buffras i minnet på din server innan de kan börja ta emot något innehåll.
Lyckligtvis är båda argumenten (req, res) strömmar, vilket innebär att vi kan skriva detta på ett mycket bättre sätt med fs.createReadStream () istället för fs.readFile ():
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);
Här .pipe () tar hand om att lyssna på "data" och "slut" händelser från fs.createReadStream (). Denna kod är inte bara renare, men nu kommer data.txt-filen att skrivas till klienter en bit i taget omedelbart när de tas emot från disken.
Rörströmmar
Läsbara strömmar kan "pipas" eller anslutas till skrivbara strömmar. Detta gör dataflödet från källströmmen till destinationsströmmen utan mycket ansträngning.
var fs = require('fs')
var readable = fs.createReadStream('file1.txt')
var writable = fs.createWriteStream('file2.txt')
readable.pipe(writable) // returns writable
När skrivbara strömmar också är läsbara strömmar, dvs när de är duplexströmmar , kan du fortsätta leda den till andra skrivbara strömmar.
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')
Läsbara strömmar kan också ledas till flera strömmar.
var readable = fs.createReadStream('source.css')
readable.pipe(zlib.createGzip()).pipe(fs.createWriteStream('output.css.gz'))
readable.pipe(fs.createWriteStream('output.css')
Observera att du måste leda till utgångsströmmarna synkront (samtidigt) innan data "flyter". Underlåtenhet att göra det kan leda till att ofullständiga data strömmas.
Observera också att strömobjekt kan avge error
; var noga med att hantera dessa händelser på ett ansvarsfullt sätt på alla strömmar efter behov:
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)
Skapa din egen läsbara / skrivbara ström
Vi kommer att se strömobjekt returneras av moduler som fs osv. Men vad händer om vi vill skapa vårt eget strömbara objekt.
För att skapa Stream-objekt måste vi använda strömmodulen från NodeJs
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);
Detta ger oss vår egen anpassningsbara skrivbara ström. vi kan implementera vad som helst inom _write- funktionen. Ovanstående metod fungerar i NodeJs 4.xx-version men i NodeJs 6.x introducerade ES6 klasser av syntax därför har syntaxen ändrats. Nedan är koden för 6.x version av NodeJs
const Writable = require('stream').Writable;
class MyWritable extends Writable {
constructor(options) {
super(options);
}
_write(chunk, encoding, callback) {
console.log(chunk);
}
}
Varför strömmar?
Låt oss undersöka följande två exempel för att läsa filens innehåll:
Den första, som använder en async-metod för att läsa en fil, och tillhandahålla en återuppringningsfunktion som kallas när filen har lästs in i minnet:
fs.readFile(`${__dirname}/utils.js`, (err, data) => {
if (err) {
handleError(err);
} else {
console.log(data.toString());
}
})
Och den andra, som använder streams
för att läsa filens innehåll, bit för bit:
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)
})
Det är värt att nämna att båda exemplen gör exakt samma sak . Vad är skillnaden då?
- Den första är kortare och ser mer elegant ut
- Den andra låter dig göra en del bearbetning på filen medan den läses (!)
När filerna du hanterar är små så har det ingen verklig effekt när du använder streams
, men vad händer när filen är stor? (så stort att det tar 10 sekunder att läsa in det i minnet)
Utan streams
väntar du och gör absolut ingenting (såvida inte din process gör andra saker), tills de 10 sekunderna passerar och filen är helt läst , och först då kan du börja bearbeta filen.
Med streams
får du filens innehåll bit för bit, precis när de är tillgängliga - och det låter dig bearbeta filen medan den läses.
Exemplet ovan illustrerar inte hur streams
kan användas för arbete som inte kan utföras när man går igenom modet, så låt oss titta på ett annat exempel:
Jag vill ladda ner en gzip
fil, packa upp den och spara dess innehåll på disken. Med tanke på filens url
är det här som behöver göras:
- Ladda ner filen
- Packa upp filen
- Spara den på disken
Här är en [liten fil] [1], som lagras i min S3
lagring. Följande kod gör ovanstående på återuppringningssättet.
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
Så här ser det ut med streams
:
s3.getObject({Bucket: 'some-bucket', Key: 'tweets.gz'}).createReadStream()
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream(`${__dirname}/tweets.json`));
// 1204 milliseconds
Jej, det är inte snabbare när du hanterar små filer - de testade 80KB
. Testa detta på en större fil, 71MB
gzipped ( 382MB
unzipped), visar att streams
är mycket snabbare
- Det tog 20925 millisekunder för att ladda ner
71MB
, packa upp den och sedan skriva382MB
till disk - med hjälp av återuppringningsmoden . - Som jämförelse tog det 13434 millisekunder att göra samma sak när du använder
streams
(35% snabbare, för en inte så stor fil)