Cuáles son los pros y los contras de fs.createReadStream vs fs.readFile en el nodo.js?


Estoy jugando con node.js y han descubierto dos formas de leer un archivo y enviarlo por el cable, una vez que he establecido que existe y he enviado el tipo MIME adecuado con 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();
});

Estoy en lo correcto al asumir que fs.createReadStream podría proporcionar una mejor experiencia de usuario si el archivo en cuestión era algo grande, como un video? Se siente como que podría ser menos bloque-ish; es esto cierto? ¿Hay otros pros, contras, advertencias o trucos que necesito saber?

Author: Kent Brewster, 2011-01-04

4 answers

Un mejor enfoque, si solo vas a conectar "data" a "write()" y "close" a "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)

El enfoque read.pipe(write) o sys.pump(read, write) tiene la ventaja de añadir también control de flujo. Por lo tanto, si el flujo de escritura no puede aceptar datos tan rápidamente, le dirá al flujo de lectura que retroceda, para minimizar la cantidad de datos que se almacenan en búfer en la memoria.

El flags:"r" y mode:0666 están implícitos por el hecho de que es un FileReadStream. El binary enconding está obsoleto if si una codificación es no especificado, solo funcionará con los búferes de datos sin procesar.

Además, puedes agregar algunas otras cosas que harán que tu archivo sea mucho más elegante:

  1. Busque req.headers.range y vea si coincide con una cadena como /bytes=([0-9]+)-([0-9]+)/. Si es así, solo desea transmitir desde esa ubicación de inicio a fin. (El número que falta significa 0 o "el final".)
  2. Hash el inodo y el tiempo de creación de la llamada a stat() en un encabezado ETag. Si obtiene un encabezado de solicitud con" if-none-match " que coincide con header, devuelve un 304 Not Modified.
  3. Verifique el encabezado if-modified-since con la fecha mtime en el objeto stat. 304 si no fue modificado desde la fecha proporcionada.

También, en general, si puede, envíe un encabezado Content-Length. (Estás declarando el archivo, así que deberías tener esto.)

 55
Author: isaacs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-04 09:10:32

fs.readFile cargará todo el archivo en la memoria como ha señalado, mientras que as fs.createReadStream leerá el archivo en trozos del tamaño que especifique.

El cliente también comenzará a recibir datos más rápido usando fs.createReadStream ya que se envían en trozos a medida que se leen, mientras que as fs.readFile leerá todo el archivo y solo entonces comenzará a enviarlo al cliente. Esto puede ser insignificante, pero puede hacer una diferencia si el archivo es muy grande y los discos son lentos.

Piense en esto, sin embargo, si se ejecuta estas dos funciones en un archivo de 100MB, la primera usará memoria de 100MB para cargar el archivo mientras que la segunda solo usará como máximo 4KB.

Editar: Realmente no veo ninguna razón por la que usarías fs.readFile especialmente porque dijiste que abrirías archivos grandes.

 35
Author: Christian Joudrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-04 04:28:45

Si es un archivo grande, entonces "readFile" acapararía la memoria, ya que almacenaría todo el contenido del archivo en la memoria y podría colgar su sistema. Mientras ReadStream lee en trozos.

Ejecute este código y observe el uso de memoria en la pestaña rendimiento del administrador de tareas.

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

De hecho, no verás " ¡hecho !!" mensaje. "readFile" no sería capaz de leer el contenido del archivo ya que buffer no es lo suficientemente grande como para contener el contenido del archivo.

Ahora, en lugar de "readFile", use ReadStream y monitor uso de memoria.

Nota: el código se toma del curso de nodo Samer buna en Pluralsight

 1
Author: Deen John,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-18 09:38:52

Otra cosa, quizás no tan conocida, es que creo que Node es mejor para limpiar la memoria no utilizada después de usar fs.readFile en comparación con fs.createReadStream. Usted debe probar esto para verificar lo que funciona mejor. Además, sé que con cada nueva versión de Node, esto ha mejorado (es decir, el recolector de basura se ha vuelto más inteligente con este tipo de situaciones).

 0
Author: carl-johan.blomqvist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-30 18:31:25