文件系统模块(fs)提供了文件读写、目录管理、流失操作等功能,本文主要介绍nodejs v20.x版本的readFile的使用.
什么是文件描述符(fd)?
文件描述符是操作系统内核为每一个打开的文件或者资源分配的一个非负整数,进程通过这个整数来标识和操作文件,而不是直接操作文件路径.可以理解为文件描述符就是进程访问文件或 I/O 资源的“编号”,本质是操作系统为打开的文件建立的一条引用。nodejs中 通过执行open方法来获取fd:
const {readFile,writeFile,open} = require('fs/promises');
async function openFile1() {
const fd = await open('test.md');
console.log(fd);
}
fd的生命周期为:
- open() -> 打开文件,检查权限,分配id
- read()/write() ->通过fd操作文件
- close() -> 释放id
上面代码如果改为:
async function openFile1() {
const fd = await open('test2.md');
console.log(1,fd);
// await fd.close();
const fd2 = await open('test2.md');
console.log(2,fd2);
}
执行结果你会发现生成了2个不同的fd,当把 await fd.close()注释去掉,结果生成的fd是一样的.
文件标识位
在 Node.js 中, 文件标识位 (flags)其实就是在 打开文件时告诉底层系统你想用什么方式来操作文件。
它的核心作用可以总结为三点:
1.决定文件的访问方式
比如:
- r → 只读
- w → 只写(会清空文件内容)
- a → 追加写
- r+ → 可读可写
- w+ → 可读可写(并清空文件)
- a+ → 可读可写(从文件尾部追加写)
也就是告诉 Node.js 我要怎么读/写这个文件。
2.控制文件是否需要新建
有些标志会影响 文件不存在时的处理方式:
- r 文件必须存在,否则报错
- w / w+ 如果文件不存在,会新建一个空文件
- a / a+ 文件不存在同样会创建新文件
3. 影响底层 I/O 行为
Node.js 的 fs 模块最终是调用 libuv → 系统调用 (open, read, write) 来完成操作。而这些 flags 实际上映射到了 操作系统级别的 open() 标志(如 Linux 的 O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_APPEND 等)。
nodejs中所有标志位:
1.读模式
Flag | 含义 | 说明 |
r | 只读 | 文件必须存在,否则报错 |
r+ | 读写 | 文件必须存在 |
async function openAndWrite() {
const fd = await open('test2.md', 'r+');
// 读取文件内容
const content = await fd.readFile({encoding: 'utf8'});
const str = '\nhello world' + Date.now();
// 写入文件
await fd.writeFile(str);
// 关闭文件
await fd.close();
}
上面代码中如果”r+”改为”r”,则会报错:bad file descriptor, write.
2.写模式
Flag | 含义 | 说明 |
w | 只写 | 文件不存在则创建,存在则清空 |
w+ | 读写 | 文件不存在则创建,存在则清空 |
async function compareWandWplus() {
// 使用 'w' 模式
const fileW = await open('example.txt', 'w');
await fileW.writeFile('New Content (w)\n');
console.log("用 'w' 写入成功");
try {
const data = await fileW.readFile({ encoding: 'utf8' });
console.log("用 'w' 读取:", data);
} catch (err) {
// 会报错:EBADF: badfile descriptor,因为w+可以读
console.error("读取失败:", err.message);
}
await fileW.close();
}
3.追加模式
Flag | 含义 | 说明 |
a | 追加写 | 文件不存在则创建,写入时总在末尾追加,不允许读取 |
a+ | 追加读写 | 文件不存在则创建;可以读写,写总在末尾追加 |
async function demoAandAplus() {
// 先准备一个文件
const initFile = await open("demo.txt", "w");
await initFile.writeFile("Hello\n");
await initFile.close();
// ---------- 模式 a ----------
console.log("=== 模式 a (append only write) ===");
const fileA = await open("demo.txt", "a");
await fileA.writeFile("World\n");
console.log("写入成功: 'World'");
try {
// a 模式下尝试读取会报错
const data = await fileA.readFile({ encoding: "utf8" });
console.log("读取:", data);
} catch (err) {
console.log("读取失败 (a 模式不支持读取):", err.message);
}
await fileA.close();
// ---------- 模式 a+ ----------
console.log("\n=== 模式 a+ (append + read) ===");
const fileAplus = await open("demo.txt", "a+");
await fileAplus.writeFile("Node.js\n");
console.log("写入成功: 'Node.js'");
// a+ 模式可以读取文件内容
const dataPlus = await fileAplus.readFile({ encoding: "utf8" });
console.log("读取成功:\n", dataPlus);
await fileAplus.close();
}
4.独占模式
Flag | 含义 | 说明 |
---|---|---|
‘wx’ | 独占写 | 类似 ‘w’,但文件已存在时报错(不会覆盖) |
‘wx+’ | 独占读写 | 类似 ‘w+’,但文件已存在时报错 |
‘ax’ | 独占追加写 | 类似 ‘a’,但文件已存在时报错 |
‘ax+’ | 独占追加读写 | 类似 ‘a+’,但文件已存在时报错 |
async function demoWxAndWxplus() {
const filename = "wx-demo.txt";
// 第一次尝试:文件不存在时,wx / wx+ 都能成功
console.log("=== 第一次创建文件 ===");
const fileWx = await open(filename, "wx");
await fileWx.writeFile("Hello from wx\n");
console.log("wx 写入成功(文件不存在时创建)");
await fileWx.close();
// 第二次尝试:文件已经存在
console.log("\n=== 文件已存在,再次尝试 ===");
try {
await open(filename, "wx");
} catch (err) {
console.log("wx 打开失败(文件已存在):", err.code); // EEXIST
}
// 使用 wx+:首次时能创建 + 读写
console.log("\n=== 使用 wx+ 模式 ===");
try {
const fileWxPlus = await open("wx-plus-demo.txt", "wx+");
await fileWxPlus.writeFile("Hello from wx+\n");
console.log("wx+ 写入成功");
const content = await fileWxPlus.readFile({ encoding: "utf8" });
console.log("wx+ 读取成功:\n", content);
await fileWxPlus.close();
} catch (err) {
console.log("wx+ 打开失败:", err.code);
}
}
5.同步模式
含义 | 说明 | |
---|---|---|
‘rs+’ | 同步读写 | 绕过内核缓存,直接操作硬盘;适合需要强一致性的场景(如数据库文件)。一般和 fs.open() 配合使用,而不是 fs.readFile 这种一次性操作。 |
在日常开发过程中,对文件的操作有多种方式,以写文件为例,可以用基于文件句柄的方式,还可以用文件路径的方式:
1.open + writeFile(基于文件句柄)
async function writeFile1 () {
const fd = await open('test-jb.md', 'w'); // 打开文件,返回 FileHandle
await fd.writeFile('test-jb'); // 通过文件句柄写入
await fd.close(); // 手动关闭
}
特点:
- 返回 FileHandle:相当于拿到一个“文件描述符”。
- 需要手动关闭:必须 close(),否则可能资源泄露。
- 更灵活:同一个 fd 可以执行多次写、读操作(如 fd.write、fd.read),还能指定偏移量、模式等。
- 适合复杂场景:
- 多次读写同一个文件。
- 需要控制文件指针位置(随机写)。
- 需要指定打开模式(w+, a+, r+, wx+ 等)
2.writeFile(文件路径)
async function writeFile2 () {
const path = 'test-jb2.md';
await writeFile(path, 'test-jb2'); // 内部自己 open -> write -> close
}
特点:
- 高层封装:内部会自动 open → write → close。
- 无需关心文件描述符:简单、方便。
- 一次性操作:只适合单次写入。
- 适合简单场景:
- 小文件写入。
- 一次性生成配置文件、日志快照等。