web-dev-qa-db-fra.com

Quels sont les avantages et les inconvénients de fs.createReadStream vs fs.readFile dans node.js?

Je mouille avec node.js et j'ai découvert deux façons de lire un fichier et de l'envoyer sur le fil, une fois que j'ai établi qu'il existe et que j'ai envoyé le type MIME approprié avec writeHead:

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

Ai-je raison de supposer que fs.createReadStream pourrait offrir une meilleure expérience utilisateur si le fichier en question était gros, comme une vidéo? Il semble que ce soit moins bloc; Est-ce vrai? Y a-t-il d'autres avantages, inconvénients, mises en garde ou accrochages que je dois savoir?

71
Kent Brewster

Une meilleure approche, si vous allez simplement raccorder "data" à "write ()" et "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)

L'approche read.pipe(write) ou sys.pump(read, write) a l'avantage d'ajouter également le contrôle de flux. Donc, si le flux d'écriture ne peut pas accepter les données aussi rapidement, il demandera au flux de lecture de se retirer, afin de minimiser la quantité de données mises en mémoire tampon.

flags:"r" Et mode:0666 Sont impliqués par le fait qu'il s'agit d'un FileReadStream. L'encodage binary est obsolète - si un encodage n'est pas spécifié, il fonctionnera simplement avec les tampons de données brutes.

En outre, vous pouvez ajouter d'autres goodies qui rendront votre fichier beaucoup plus simple:

  1. Détectez req.headers.range Et voyez si cela correspond à une chaîne comme /bytes=([0-9]+)-([0-9]+)/. Si c'est le cas, vous souhaitez simplement diffuser de cet emplacement de début à la fin. (Le nombre manquant signifie 0 ou "la fin".)
  2. Hachage de l'inode et du temps de création de l'appel stat () dans un en-tête ETag. Si vous obtenez un en-tête de demande avec "if-none-match" correspondant à cet en-tête, renvoyez un 304 Not Modified.
  3. Vérifiez l'en-tête if-modified-since Par rapport à la date mtime sur l'objet stat. 304 s'il n'a pas été modifié depuis la date indiquée.

De plus, en général, si vous le pouvez, envoyez un en-tête Content-Length. (Vous êtes stat- le fichier, vous devriez donc l'avoir.)

57
isaacs

fs.readFile chargera tout le fichier en mémoire comme vous l'avez souligné, tandis que fs.createReadStream lira le fichier en morceaux de la taille que vous spécifiez.

Le client commencera également à recevoir des données plus rapidement en utilisant fs.createReadStream tel qu'il est envoyé par blocs lors de sa lecture, tandis que fs.readFile lira l'intégralité du fichier et commencera ensuite à l'envoyer au client. Cela peut être négligeable, mais peut faire une différence si le fichier est très volumineux et que les disques sont lents.

Pensez à cela cependant, si vous exécutez ces deux fonctions sur un fichier de 100 Mo, la première utilisera 100 Mo de mémoire pour charger le fichier alors que cette dernière n'utilisera que 4 Ko au maximum.

Edit: je ne vois vraiment aucune raison pour laquelle vous utiliseriez fs.readFile d'autant plus que vous avez dit que vous alliez ouvrir de gros fichiers.

38
Christian Joudrey

S'il s'agit d'un gros fichier, "readFile" monopolise la mémoire car il met en mémoire tampon tout le contenu du fichier dans la mémoire et peut bloquer votre système. Pendant que ReadStream lit en morceaux.

Exécutez ce code et observez l'utilisation de la mémoire dans l'onglet performances du gestionnaire de tâches.

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

En fait, vous ne verrez pas "terminé !!" message. "readFile" ne pourrait pas lire le contenu du fichier car le tampon n'est pas assez grand pour contenir le contenu du fichier.

Maintenant, au lieu de "readFile", utilisez readStream et surveillez l'utilisation de la mémoire.

Remarque: le code est tiré de Samer buna Node course on Pluralsight

3
Deen John

Une autre chose, peut-être pas si connue, est que je crois que Node est mieux pour nettoyer la mémoire non utilisée après avoir utilisé fs.readFile par rapport à fs.createReadStream. Vous devez tester cela pour vérifier ce qui fonctionne le mieux. De plus, je sais que chaque nouvelle version de Node s'est améliorée (c'est-à-dire que le garbage collector est devenu plus intelligent avec ce type de situations).

0