Node.js 的文件流(File Streams)是非常强大且高效的工具,特别适用于处理大文件、流式数据或需要高效文件操作的场景。fs
模块提供了流(Stream)接口,允许你以异步、非阻塞的方式处理文件的读取和写入。
在 Node.js 中,流分为 读流 和 写流,它们的核心是通过流动的方式将数据传输处理,而不是一次性加载整个文件。这能显著减少内存使用并提升性能。
流(Stream)基础
流是一种处理数据的方式,数据是逐步流动的(而不是一次性加载)。流分为四种类型:
- Readable Stream:用于读取数据。
- Writable Stream:用于写入数据。
- Duplex Stream:既可读又可写。
- Transform Stream:对数据进行转换。
文件流通常是 Readable
或 Writable
类型。
🧱 一、文件流的基本概念
在 Node.js 中,文件流主要通过 fs.createReadStream()
和 fs.createWriteStream()
来操作,它们是基于 stream.Readable
和 stream.Writable
的。
1. 读取文件流 - fs.createReadStream()
fs.createReadStream()
用于从文件中读取数据,并将数据以流的形式逐步传输到内存中。这样可以避免一次性加载整个文件,从而减少内存消耗。
语法:
js
fs.createReadStream(path[, options])
- path: 文件路径。
- options: 可选配置项,如编码方式、开始和结束位置等。
以下是 fs.createReadStream(path, options)
的第二个参数(options
)的可选配置项总结表:
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
flags |
string |
'r' |
文件打开模式,例如 'r' 代表只读 |
encoding |
string 或 null |
null |
指定读取数据的编码,如 'utf8' ,不设则返回 Buffer |
fd |
number |
null |
已打开文件的描述符,避免重复打开 |
mode |
number |
0o666 |
设置文件权限(仅在创建时有效) |
autoClose |
boolean |
true |
流结束时是否自动关闭文件 |
start |
number |
0 |
从文件的哪个字节开始读取 |
end |
number |
null |
读取到哪个字节结束(包含该字节) |
highWaterMark |
number |
64 * 1024 |
每次读取的最大字节数(缓冲区大小) |
emitClose |
boolean |
false |
是否在流关闭时触发 'close' 事件 |
示例:读取文件流
js
const fs = require('fs');
// 创建一个可读流
const readableStream = fs.createReadStream('example.txt', 'utf8');
// 监听 'data' 事件,逐块读取数据
readableStream.on('data', (chunk) => {
console.log('读取的数据块:', chunk);
});
// 监听 'end' 事件,表示数据读取完成
readableStream.on('end', () => {
console.log('文件读取完毕');
});
// 监听 'error' 事件,处理可能的错误
readableStream.on('error', (err) => {
console.error('读取文件时出错:', err);
});
'data'
事件 :每当读取到一个数据块时,data
事件会触发。'end'
事件:当文件全部读取完成后触发。'error'
事件:发生错误时触发。
2. 写入文件流 - fs.createWriteStream()
fs.createWriteStream()
用于向文件中写入数据,并将数据以流的方式逐步写入。这种方式特别适合写入大文件或者需要高效写入的场景。
语法:
js
fs.createWriteStream(path[, options])
- path: 文件路径。
- options : 可选配置项,类似于
createReadStream()
。
以下是 fs.createWriteStream(path, options)
的第二个参数(options
)的可选配置项总结表:
配置项 | 类型 | 默认值 | 说明 |
---|---|---|---|
flags |
string |
'w' |
文件打开模式,如 'w' 表示写入,必要时创建文件 |
encoding |
string |
'utf8' |
写入数据时使用的编码 |
fd |
number |
null |
已打开的文件描述符,传入时不会再自动打开文件 |
mode |
number |
0o666 |
文件权限设置(创建文件时适用) |
autoClose |
boolean |
true |
写入完成或出错时是否自动关闭文件 |
emitClose |
boolean |
false |
是否在关闭时触发 'close' 事件 |
start |
number |
undefined |
从文件的哪个字节开始写入 |
highWaterMark |
number |
16 * 1024 |
内部缓冲区大小,影响 write() 返回值(流控制) |
示例:写入文件流
js
const fs = require('fs');
// 创建一个可写流
const writableStream = fs.createWriteStream('output.txt');
// 向文件写入数据
writableStream.write('这是第一行内容\n');
writableStream.write('这是第二行内容\n');
// 结束写入,关闭流
writableStream.end('写入完成');
// 监听 'finish' 事件,表示写入完成
writableStream.on('finish', () => {
console.log('数据成功写入文件');
});
// 监听 'error' 事件,处理写入错误
writableStream.on('error', (err) => {
console.error('写入文件时出错:', err);
});
'finish'
事件:当所有数据成功写入并且流关闭时触发。'error'
事件:发生写入错误时触发。
3. ### fs.createReadStream
和 fs.createWriteStream
事件总结
事件名 | 触发条件 | 适用对象 | 说明 |
---|---|---|---|
open |
文件成功打开 | createReadStream / createWriteStream |
当文件被打开时触发,返回文件描述符(fd ) |
close |
流关闭后触发 | createReadStream / createWriteStream |
文件流关闭时触发,表示文件不再被使用 |
data |
数据被读取时触发 | createReadStream |
每次读取数据时触发,data 事件会传递读取到的数据 |
end |
数据读取完毕时触发 | createReadStream |
当没有更多数据可读取时触发 |
finish |
数据写入完成时触发 | createWriteStream |
当数据成功写入流并被刷新到文件时触发 |
drain |
内部缓冲区清空后触发 | createWriteStream |
当缓冲区的写入数据量少于 highWaterMark ,且可继续写入时触发(也就是写入时超过了缓冲区, 等缓冲区清空时就会触发,如果缓冲区一直没有撑满,则不会触发) |
error |
遇到错误时触发 | createReadStream / createWriteStream |
读取或写入过程发生错误时触发 |
pipe |
流被传递时触发 | createReadStream |
当流的数据被"管道"传输到另一个流时触发 |
unpipe |
流的管道解除时触发 | createReadStream |
当流被解除管道时触发 |
🛠️ 二、流的管道机制:pipe()
方法
Node.js 提供了流的管道机制,利用 .pipe()
方法可以将一个流直接连接到另一个流。这种方式特别适合文件的复制、数据处理等任务。
示例:使用 .pipe()
复制文件
js
const fs = require('fs');
// 创建可读流
const readableStream = fs.createReadStream('source.txt');
// 创建可写流
const writableStream = fs.createWriteStream('destination.txt');
// 使用 pipe 直接将源文件的数据流传输到目标文件
readableStream.pipe(writableStream);
// 监听 'finish' 事件,表示复制完成
writableStream.on('finish', () => {
console.log('文件复制完成');
});
通过 .pipe()
,你可以轻松地将数据从一个流传递到另一个流,且自动处理了数据的流动、内存管理等细节。
🧠 三、流的高效处理:缓冲区和背压
1. 背压(Backpressure) :
背压是指流速过快时,目标流可能无法及时处理数据的情况。当一个可写流的缓冲区满时,源流会暂停数据的传输,直到目标流有足够空间。这是 Node.js 中流的一个非常重要的特性,有助于避免内存溢出和性能下降。
2. 缓冲区(Buffer) :
Node.js 使用缓冲区来处理流中的数据块,缓冲区是一个临时存储区域,用于存储流中的数据块,直到它们被进一步处理。
3. 文件读取写入时产生背压问题以及解决的流程图
