在 Node.js 中,我们通常使用 readFile
或 writeFile
方法来读取或写入文件。这些方法虽然简单易用,但在某些场景下存在明显的局限性:
- 缺乏精细控制:传统方法会一次性将整个文件加载到内存中,无法实现按需读取。例如,我们无法从特定位置开始读取,也无法每次读取固定大小的字节。
- 无法暂停与恢复:在处理大文件时,可能需要暂停读取以执行其他任务,再继续读取。然而,传统方法无法提供这样的灵活性。
- 处理大文件效率低下:对大型文件进行一次性读取会占用大量内存资源,不仅影响程序性能,还可能导致内存溢出。
为了解决这些问题,Node.js 提供了「数据流(Stream)」的机制。当处理大型二进制文件时,数据流不会将整个文件一次性加载到内存中,而是将文件的数据分块读取,每次读取一小部分字节,逐步加载并处理,直到文件完全读取完毕。这种分块读取和写入的方式不仅显著提高了处理效率,还节省了内存资源。
分类
流类型 | 名称 | 功能描述 | 使用场景 |
---|---|---|---|
Readable | 可读流 | 用于从数据源读取数据,例如读取文件内容。 | 逐步获取数据,避免一次性加载整个文件。 |
Writable | 可写流 | 用于向目标写入数据,例如写入文件。 | 分块写入大文件或实时数据。 |
Duplex | 双工流 | 同时支持数据的读取和写入,例如用于网络通信中的 Socket。 | 在同一通道中实现双向数据传输。 |
Transform | 转换流 | 在读取和写入的过程中对数据进行修改或转换。 | 压缩数据、加密数据或改变数据格式。 |
只读流
字段名 | 类型 | 默认值 | 描述 |
---|---|---|---|
flags | string | 'r' | 文件系统标志,通常为 'r' 表示读取模式。 |
encoding | string | null | 文件内容的编码,例如 'utf8' 或 'base64' 。默认为 null ,表示返回 Buffer。 |
fd | number | null | 文件描述符。如果提供了 fd ,则忽略 path 。 |
mode | number | 0o666 | 如果文件需要创建,则设置其权限(仅对写入有效)。 |
autoClose | boolean | true | 如果为 true ,当流结束或发生错误时自动关闭文件描述符。 |
emitClose | boolean | true | 如果为 true ,流关闭时会触发 'close' 事件。 |
start | number | undefined | 文件读取的起始字节位置。 |
end | number | undefined | 文件读取的结束字节位置**(包含)**。 |
highWaterMark | number | 64 * 1024 (64KB) | 每次读取的缓冲区大小(字节数)。 high-water-mark => 最高水位线 也就是一次可以读取的字节数 |
// 文件流在fs下,不在fs/promises下
import fs from 'node:fs';
let str = '';
// 读取 第5 到 第20「 包含 」 个字节的字符,每次读取 10个字节
const readStream = fs.createReadStream('./assets/users.json', {
start: 5,
end: 20,
highWaterMark: 10,
});
// 监听读取事件 => 每次读取到数据时触发
readStream.on('data', (chunk) => {
str += chunk;
});
// 监听读取结束事件 => 读取结束时触发
readStream.on('end', () => {
console.log('读取结束');
console.log(str);
});
// 监听读取错误事件 => 读取错误时触发
readStream.on('error', (err) => {
console.log(err);
});
// 监听文件打开事件 => 文件打开时触发
readStream.on('open', () => {
console.log('文件打开');
});
// 监听文件关闭事件 => 文件关闭时触发
readStream.on('close', () => {
console.log('文件关闭');
});
import fs from 'node:fs';
const readStream = fs.createReadStream('./assets/users.json', {
start: 5,
end: 20,
highWaterMark: 10,
});
readStream.on('data', (chunk) => {
console.log('读取到的数据:', chunk.toString());
readStream.pause(); // 暂停读取
console.log('暂停 2 秒后继续读取...');
setTimeout(() => {
readStream.resume(); // 恢复读取
}, 2000);
});
需要注意的是,当文件读取完成时,系统会自动关闭文件,你不需要手动调用close方法。
当然,如果你想在读取过程中手动关闭文件,也可以调用destroy方法。
readable.destroy();
写入流
配置项 | 写入流(Writable Stream) | 备注 |
---|---|---|
flags | 'w' (写入模式,默认值),可设置为'a' (追加写入模式) | 写入流可以选择覆盖写入或追加写入。 |
encoding | 'utf8' (默认值) | 写入流默认编码为UTF-8。 |
fd | null (默认值,自动分配文件描述符) | 文件描述符,通常不需要手动设置。 |
mode | 0o666 (文件权限,默认值) | 文件权限,默认值为可读写权限。 |
autoClose | true (写入完成后自动关闭文件,默认值) | 自动关闭文件的设置。 |
start | 0 (从文件的第0字节开始写入,默认值) | 起始字节位置,写入流支持指定写入起始位置。 |
highWaterMark | 16 * 1024 (每次写入的最大数据量,默认16KB) | 控制流的缓冲区大小,写入流默认较小。 |
import fs from 'node:fs';
// 创建一个写入流
const writeStream = fs.createWriteStream('./foo.txt');
// write方法可以多次调用,每次都会将数据追加到文件中
writeStream.write('Hello\t');
writeStream.write('World\n');
writeStream.write('Hello\t');
writeStream.write('Node.js\n');
// 写入流需要手动关闭 或者等脚本执行完自动关闭
// 不过node运行于服务器,一般脚本24小时运行,所以推荐手动关闭
writeStream.close();
// 监听文件打开事件 「 回调的参数是文件描述符 」
writeStream.on('open', fd => {
console.log('文件打开', fd);
});
// 监听文件写入完成事件
writeStream.on('finish', () => {
console.log('文件写入完成');
});
// 监听文件关闭事件
writeStream.on('close', () => {
console.log('文件关闭');
});
// 监听文件写入错误事件
writeStream.on('error', (err) => {
console.log(err);
});
close vs end
end
方法等同于以下两步操作:
- 将最后的内容写入流;
- 结束流并关闭底层资源(如文件描述符)。
例如:
writeStream.end('Goodbye\n');
等价于:
writeStream.write('Goodbye\n');
writeStream.end();
控制写入位置
import fs from 'node:fs';
const writeStream = fs.createWriteStream('foo.txt', { flag: 'w', start: 5 });
writeStream.write('Hello');
writeStream.write('World');
writeStream.close();
pipe
pipe
方法可理解为“管道”,用于在可读流和可写流之间建立管道。这样就可以将可读流中的所有数据直接输入到对应的可写流中。
:: code-group
import fs from 'node:fs';
// 创建写入流 和 读取流
const readStream = fs.createReadStream('foo.txt');
const writeStream = fs.createWriteStream('boo_copy.txt');
// 通过读取流 建立数据传输通道
readStream.pipe(writeStream);
// 监听读取流结束事件
readStream.on('end', () => {
console.log('拷贝完成');
});
import fs from 'node:fs';
const readStream = fs.createReadStream('foo.txt');
const writeStream = fs.createWriteStream('boo_copy.txt');
readStream.on('data', (chunk) => {
writeStream.write(chunk);
});
// 监听读取流结束事件
readStream.on('end', () => {
console.log('拷贝完成');
});
::