在node.js中,fs.createReadStream和fs.readFile有什么优缺点?

79

我正在使用 node.js 进行一些操作,发现了两种读取文件并将其发送到网络的方法。在确保文件存在并使用 writeHead 发送正确的 MIME 类型后:

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

如果涉及到大文件,比如视频,使用fs.createReadStream是否会提供更好的用户体验呢?感觉它可能会减少阻塞,这是真的吗?还有其他的优点、缺点、警告或需要了解的问题吗?


我知道这是一个老问题,但readFile的用例之一是对数据进行排序,而在流处理中无法做到这一点,例如:一个包含数字列表的文件,您的程序需要对其进行排序...有一些方法可以在流上进行排序,但接收方也必须能够解释它们。 - Laukik
当我提出这个问题时,这正是我正在做的事情。 :) - Kent Brewster
4个回答

62

如果你只是想将"data"连接到"write()",并将"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)
read.pipe(write)sys.pump(read, write) 的方式还有一个好处,就是添加了流量控制。因此,如果写入流不能快速接收数据,它会告诉读取流要减缓速度,以最小化内存中缓冲的数据量。 flags:"r"mode:0666 是由于它是FileReadStream而隐含的。二进制编码已过时,如果没有指定编码,则将使用原始数据缓冲区处理。
此外,您还可以添加其他一些好处,使文件服务更加出色:
  1. 嗅探req.headers.range并查看是否与类似于/bytes=([0-9]+)-([0-9]+)/的字符串匹配。如果是,则只需从该开始位置流式传输到结束位置。(缺少数字表示0或“结尾”。)
  2. 从stat()调用中的inode和创建时间生成ETag标头的哈希值。如果您收到的请求标头与该标头匹配的"if-none-match",则发送304未修改内容
  3. 检查if-modified-since标头是否与stat对象上的mtime日期匹配。如果自提供的日期以来未修改,则为304。
此外,一般情况下,如果可以,则发送Content-Length标头。(您正在使用stat函测量文件大小,因此应该有这个。)

@isaacs,您能否提供一个实现这三个步骤的示例,谢谢! - Eugene Kuzmenko
1
“bufferSize”选项已被弃用,建议使用“highWaterMark”选项。 - Umair Ishaq
4
这句话的意思是“这句话怎么回答了最初提出的问题?” - user5228393

47

fs.readFile会将整个文件加载到内存中,而fs.createReadStream会按指定大小将文件以块的形式读取。

使用fs.createReadStream时,客户端会更快地开始接收数据,因为它是在读取时分块发送的,而使用fs.readFile则会先将整个文件读取出来,然后才开始向客户端发送。这可能可以忽略不计,但如果文件非常大且磁盘速度较慢,则可能会有所区别。

请思考一下,如果您对一个100MB的文件运行这两个函数,则第一个函数将使用100MB的内存来加载文件,而后者最多只会使用4KB。

编辑:我真的不明白为什么你会使用fs.readFile,特别是你说你将打开大文件。


1
这意味着我们无法使用fs.readFile方法捕获进度,例如是吗? - Obzzen

6
如果是大文件,那么“readFile”会占用内存,因为它会在内存中缓存所有文件内容,并可能挂起系统。而“ReadStream”则会逐块读取。运行此代码并观察任务管理器的性能选项卡中的内存使用情况。
 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 !!");
});

实际上,您不会看到“done !!”消息。 由于缓冲区不足以容纳文件内容,“readFile”无法读取文件内容。

现在,改用readStream并监视内存使用情况。

注意:代码摘自Pluralsight上的Samer buna Node课程。


0
另外,也许不是很出名的一件事情是,我认为相比于fs.createReadStream,Node在使用fs.readFile后更擅长清理未使用的内存。你应该测试一下以确定哪种方法最好。此外,我知道随着每个新版本的Node发布,这方面已经变得越来越好了(即垃圾回收器在这些类型的情况下变得更加智能)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接