目录
-
- [1. 引言](#1. 引言)
- [2. 文件写入](#2. 文件写入)
-
- [2.1 writeFile 异步写入](#2.1 writeFile 异步写入)
- [2.2 writeFileSync 同步写入](#2.2 writeFileSync 同步写入)
- [2.3 appendFile / appendFileSync 追加写入](#2.3 appendFile / appendFileSync 追加写入)
- [2.4 createWriteStream 流式写入](#2.4 createWriteStream 流式写入)
- [2.5 写入文件的场景](#2.5 写入文件的场景)
- [3. 文件读取](#3. 文件读取)
-
- [3.1 readFile 异步读取](#3.1 readFile 异步读取)
- [3.2 readFileSync 同步读取](#3.2 readFileSync 同步读取)
- [3.3 createReadStream 流式读取](#3.3 createReadStream 流式读取)
- [3.4 读取文件应用场景](#3.4 读取文件应用场景)
- [4. 文件移动与重命名](#4. 文件移动与重命名)
- [5. 文件删除](#5. 文件删除)
-
- [5.1 unlink / unlinkSync 删除文件](#5.1 unlink / unlinkSync 删除文件)
- [5.2 rm / rmSync 删除文件或文件夹](#5.2 rm / rmSync 删除文件或文件夹)
- [5.3 unlink 与 rm 的区别](#5.3 unlink 与 rm 的区别)
- [6. 文件夹操作](#6. 文件夹操作)
-
- [6.1 mkdir 创建文件夹](#6.1 mkdir 创建文件夹)
- [6.2 readdir 读取文件夹](#6.2 readdir 读取文件夹)
- [6.3 rmdir 删除文件夹](#6.3 rmdir 删除文件夹)
- [7. 查看资源状态](#7. 查看资源状态)
- [8. 相对路径问题](#8. 相对路径问题)
- [9. __dirname](#9. __dirname)
- [10. 综合练习](#10. 综合练习)
-
- [10.1 进阶练习:文件管理小工具](#10.1 进阶练习:文件管理小工具)
- [10.2 进阶练习:批量重命名文件](#10.2 进阶练习:批量重命名文件)
- [11. 总结](#11. 总结)
1. 引言
在 Node.js 开发中,文件操作是几乎每个项目都会遇到的需求------无论是读取配置文件、写入日志、还是处理上传的文件。而这一切的核心,就是 Node.js 内置的 fs 模块(File System,文件系统模块)。
fs 模块提供了丰富的 API,让我们可以用 JavaScript 轻松地操作文件和文件夹。本文将用最通俗易懂的方式,带你从零开始掌握 fs 模块的核心用法,包括:
- 文件的写入与读取
- 文件的移动、重命名与删除
- 文件夹的创建、读取与删除
- 查看资源状态
- 相对路径与
__dirname的坑
前置准备 :确保你已经安装了 Node.js(建议 v14+),并创建了一个项目文件夹,在文件夹内新建一个
index.js文件用于练习。
2. 文件写入
文件写入是 fs 模块最基础的操作之一。Node.js 提供了多种写入方式,适用于不同的场景。
2.1 writeFile 异步写入
writeFile 是异步方法,它不会阻塞主线程,写入完成后通过回调函数通知我们。
javascript
const fs = require('fs');
// 异步写入文件
fs.writeFile('./hello.txt', '你好,Node.js!', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('文件写入成功!');
});
console.log('这行代码会先执行,因为 writeFile 是异步的');
运行结果:
这行代码会先执行,因为 writeFile 是异步的
文件写入成功!
要点:
- 如果文件不存在,
writeFile会自动创建。 - 如果文件已存在,
writeFile会覆盖原有内容。 - 回调函数的第一个参数是错误对象
err,写入成功时为null。
2.2 writeFileSync 同步写入
writeFileSync 是同步方法,代码会等待文件写入完成后再继续执行。
javascript
const fs = require('fs');
// 同步写入文件
try {
fs.writeFileSync('./hello.txt', '这是同步写入的内容');
console.log('文件写入成功!');
} catch (err) {
console.error('写入失败:', err);
}
console.log('这行代码会等写入完成后才执行');
运行结果:
文件写入成功!
这行代码会等写入完成后才执行
要点:
- 同步方法会阻塞主线程,在写入大文件或高频操作时需谨慎使用。
- 建议用
try...catch包裹,捕获可能的错误。
2.3 appendFile / appendFileSync 追加写入
如果不想覆盖原有内容,而是追加 新内容,可以使用 appendFile(异步)和 appendFileSync(同步)。
javascript
const fs = require('fs');
// 异步追加写入
fs.appendFile('./log.txt', '第一条日志\n', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('日志追加成功!');
});
// 同步追加写入
fs.appendFileSync('./log.txt', '第二条日志\n');
运行结果 (log.txt 内容):
第一条日志
第二条日志
要点:
- 追加写入不会覆盖原有内容,而是在文件末尾添加。
- 常用于日志记录、数据累积等场景。
2.4 createWriteStream 流式写入
当需要写入大文件 (如视频、日志文件)时,一次性写入会占用大量内存。此时应使用流式写入。
javascript
const fs = require('fs');
// 创建写入流
const writeStream = fs.createWriteStream('./big-file.txt');
// 分多次写入
for (let i = 0; i < 1000; i++) {
writeStream.write(`第 ${i} 行数据\n`);
}
// 写入完成
writeStream.end(() => {
console.log('流式写入完成!');
});
// 监听错误
writeStream.on('error', (err) => {
console.error('流式写入出错:', err);
});
要点:
- 流式写入是边写边存,内存占用低,适合大文件。
- 写入完成后需要调用
end()方法关闭流。
2.5 写入文件的场景
| 写入方式 | 适用场景 |
|---|---|
writeFile |
小文件、配置文件的覆盖写入 |
writeFileSync |
需要立即确认写入结果的简单脚本 |
appendFile |
日志追加、数据累积 |
createWriteStream |
大文件、高频写入、视频/日志流 |
3. 文件读取
读取文件同样有多种方式,与写入一一对应。
3.1 readFile 异步读取
javascript
const fs = require('fs');
// 异步读取文件
fs.readFile('./hello.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
console.log('这行代码会先执行');
要点:
- 第二个参数
'utf8'指定编码格式,不传则返回 Buffer(二进制数据)。 - 异步读取不会阻塞主线程。
3.2 readFileSync 同步读取
javascript
const fs = require('fs');
try {
const data = fs.readFileSync('./hello.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
要点:
- 同步读取会阻塞主线程,适合小文件或启动时的配置读取。
3.3 createReadStream 流式读取
javascript
const fs = require('fs');
// 创建读取流
const readStream = fs.createReadStream('./big-file.txt', 'utf8');
// 监听数据事件
readStream.on('data', (chunk) => {
console.log('读取到一段数据:', chunk.length, '字符');
});
// 读取完成
readStream.on('end', () => {
console.log('文件读取完成!');
});
// 监听错误
readStream.on('error', (err) => {
console.error('读取出错:', err);
});
要点:
- 流式读取将文件分成多个
chunk(数据块)依次读取,内存占用低。 - 适合大文件、视频流、日志分析等场景。
3.4 读取文件应用场景
| 读取方式 | 适用场景 |
|---|---|
readFile |
小文件、配置文件读取 |
readFileSync |
启动时同步加载配置 |
createReadStream |
大文件、流式处理、视频播放 |
4. 文件移动与重命名
使用 rename 方法可以移动 或重命名文件。
javascript
const fs = require('fs');
// 重命名文件
fs.rename('./hello.txt', './hello-new.txt', (err) => {
if (err) {
console.error('重命名失败:', err);
return;
}
console.log('重命名成功!');
});
// 移动文件(把文件移动到子文件夹)
fs.rename('./hello-new.txt', './backup/hello-new.txt', (err) => {
if (err) {
console.error('移动失败:', err);
return;
}
console.log('移动成功!');
});
要点:
rename既可以重命名,也可以移动文件(跨目录)。- 目标路径的文件夹必须存在,否则会报错。
5. 文件删除
删除文件可以使用 unlink / unlinkSync,也可以使用 rm / rmSync。下面分别介绍。
5.1 unlink / unlinkSync 删除文件
unlink 是异步 删除,unlinkSync 是同步 删除,它们只能删除文件(不能删除文件夹)。
javascript
const fs = require('fs');
// 异步删除文件
fs.unlink('./hello-new.txt', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('文件删除成功!');
});
// 同步删除文件
fs.unlinkSync('./temp.txt');
要点:
- 删除不存在的文件会报错,建议先判断文件是否存在(见第七节)。
- 删除操作不可恢复,请谨慎使用。
5.2 rm / rmSync 删除文件或文件夹
rm 是异步 删除,rmSync 是同步 删除。与 unlink 不同,rm 既可以删除文件 ,也可以删除文件夹(包括非空文件夹),功能更强大。
javascript
const fs = require('fs');
// 异步删除文件
fs.rm('./hello-new.txt', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('文件删除成功!');
});
// 同步删除文件
fs.rmSync('./temp.txt');
// 递归删除文件夹(包括里面的所有内容)
fs.rmSync('./data', { recursive: true, force: true });
要点:
- 删除非空文件夹 时,需要设置
{ recursive: true }。 - 设置
{ force: true }后,删除不存在的文件/文件夹不会报错,相当于「忽略错误」。 rm是 Node.js 14.14+ 提供的新方法,推荐在新项目中使用。
5.3 unlink 与 rm 的区别
| 对比项 | unlink / unlinkSync |
rm / rmSync |
|---|---|---|
| 删除文件 | ✅ 可以 | ✅ 可以 |
| 删除文件夹 | ❌ 不可以 | ✅ 可以(需 recursive: true) |
| 删除不存在的目标 | 报错 | 报错(除非 force: true) |
| 出现版本 | 早期就有 | Node.js 14.14+ |
| 推荐程度 | 仅删文件时可用 | 更通用,推荐 |
一句话总结 :unlink 只能删文件,rm 既能删文件也能删文件夹,功能更全面,新项目推荐优先使用 rm。
6. 文件夹操作
6.1 mkdir 创建文件夹
javascript
const fs = require('fs');
// 创建单层文件夹
fs.mkdir('./data', (err) => {
if (err) {
console.error('创建失败:', err);
return;
}
console.log('文件夹创建成功!');
});
// 创建多层文件夹(需要 recursive: true)
fs.mkdir('./data/sub1/sub2', { recursive: true }, (err) => {
if (err) {
console.error('创建失败:', err);
return;
}
console.log('多层文件夹创建成功!');
});
要点:
- 创建多层嵌套文件夹时,必须设置
{ recursive: true },否则会报错。
6.2 readdir 读取文件夹
javascript
const fs = require('fs');
// 读取文件夹内容
fs.readdir('./data', (err, files) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件夹内容:', files);
});
运行结果:
文件夹内容: [ 'sub1', 'sub2' ]
要点:
readdir返回的是文件夹内的文件名/文件夹名数组。
6.3 rmdir 删除文件夹
rmdir 用于删除文件夹,但它只能删除空文件夹 。如果要删除非空文件夹,需要设置 { recursive: true }。
javascript
const fs = require('fs');
// 删除空文件夹
fs.rmdir('./data/sub1', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('文件夹删除成功!');
});
// 删除非空文件夹(需要 recursive: true)
fs.rmdir('./data', { recursive: true }, (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('非空文件夹删除成功!');
});
要点:
- 删除非空 文件夹时,必须设置
{ recursive: true },否则会报错。 - 删除操作不可恢复,请谨慎使用。
⚠️ 注意(重要) :使用
fs.rmdir(path, { recursive: true })递归删除时,Node.js 会弹出如下弃用警告(DeprecationWarning):
(node:31999) [DEP0147] DeprecationWarning: In future versions of Node.js, fs.rmdir(path, { recursive: true }) will be removed. Use fs.rm(path, { recursive: true }) instead意思是:在未来的 Node.js 版本中,
fs.rmdir(path, { recursive: true })这种递归删除的用法会被移除 ,官方推荐改用fs.rm(path, { recursive: true })。
推荐做法 :如果需要递归删除文件夹(尤其是非空文件夹),建议直接使用 rm / rmSync,避免弃用警告,也更符合官方推荐:
javascript
const fs = require('fs');
// 推荐:使用 rm 递归删除文件夹(不会触发弃用警告)
fs.rm('./data', { recursive: true, force: true }, (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('文件夹删除成功!');
});
// 同步写法
fs.rmSync('./data', { recursive: true, force: true });
小结:
rmdir删除空文件夹没问题,可以继续用。- 删除非空文件夹 时,
rmdir的recursive: true已弃用 ,请改用rm/rmSync的recursive: true。
7. 查看资源状态
使用 stat 方法可以查看文件或文件夹的详细信息。
javascript
const fs = require('fs');
// 查看文件状态
fs.stat('./hello.txt', (err, stats) => {
if (err) {
console.error('获取状态失败:', err);
return;
}
console.log('是否为文件:', stats.isFile());
console.log('是否为文件夹:', stats.isDirectory());
console.log('文件大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
});
运行结果:
是否为文件: true
是否为文件夹: false
文件大小: 23 字节
创建时间: 2026-10-11T07:23:18.000Z
修改时间: 2026-10-11T07:23:18.000Z
要点:
stats.isFile()判断是否为文件。stats.isDirectory()判断是否为文件夹。stats.size获取文件大小(字节)。
实用技巧:判断文件是否存在
javascript
const fs = require('fs');
// 判断文件是否存在
fs.stat('./hello.txt', (err, stats) => {
if (err) {
console.log('文件不存在');
return;
}
console.log('文件存在,大小:', stats.size);
});
8. 相对路径问题
在 Node.js 中,使用相对路径(如 ./hello.txt)时,路径是相对于当前工作目录 (即运行 node 命令的目录)解析的,而不是相对于当前文件。
javascript
// 假设你在 /home/user/project 目录下运行 node index.js
// 而 index.js 在 /home/user/project/src 目录下
const fs = require('fs');
// 这里的 ./hello.txt 会解析为 /home/user/project/hello.txt
// 而不是 /home/user/project/src/hello.txt
fs.readFile('./hello.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log(data);
});
问题 :如果从其他目录运行 node src/index.js,相对路径就会出错。
解决方案 :使用 __dirname(见第九节)。
9. __dirname
__dirname 是 Node.js 提供的全局变量 ,表示当前文件所在的目录的绝对路径。
javascript
const fs = require('fs');
const path = require('path');
// __dirname 表示当前文件所在目录的绝对路径
console.log('当前文件目录:', __dirname);
// 使用 __dirname 拼接路径,无论从哪里运行都不会出错
const filePath = path.join(__dirname, 'hello.txt');
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
要点:
__dirname始终指向当前文件所在的目录,与运行命令的位置无关。他不会随着工作目录的变化而变化。- 推荐使用
path.join()拼接路径,避免手动拼接字符串带来的分隔符问题。
对比:
| 方式 | 解析基准 | 是否可靠 |
|---|---|---|
./hello.txt |
当前工作目录 | 不可靠,取决于运行位置 |
__dirname + '/hello.txt' |
当前文件目录 | 可靠,始终正确 |
path.join(__dirname, 'hello.txt') |
当前文件目录 | 最可靠,推荐 |
10. 综合练习
10.1 进阶练习:文件管理小工具
让我们把今天学到的知识综合起来,写一个文件管理小工具,实现以下功能:
- 创建
data文件夹 - 在
data文件夹中写入一个user.txt文件 - 读取并打印文件内容
- 追加一行新内容
- 重命名文件为
user-info.txt - 查看文件状态
- 删除文件
- 删除文件夹
javascript
const fs = require('fs');
const path = require('path');
// 使用 __dirname 确保路径可靠
const dataDir = path.join(__dirname, 'data');
const filePath = path.join(dataDir, 'user.txt');
// 1. 创建文件夹
fs.mkdir(dataDir, { recursive: true }, (err) => {
if (err) {
console.error('创建文件夹失败:', err);
return;
}
console.log('✅ 文件夹创建成功');
// 2. 写入文件
fs.writeFile(filePath, '姓名:张三\n年龄:25\n', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('✅ 文件写入成功');
// 3. 读取文件
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('📄 文件内容:');
console.log(data);
// 4. 追加内容
fs.appendFile(filePath, '城市:北京\n', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('✅ 内容追加成功');
// 5. 重命名文件
const newFilePath = path.join(dataDir, 'user-info.txt');
fs.rename(filePath, newFilePath, (err) => {
if (err) {
console.error('重命名失败:', err);
return;
}
console.log('✅ 文件重命名为 user-info.txt');
// 6. 查看文件状态
fs.stat(newFilePath, (err, stats) => {
if (err) {
console.error('获取状态失败:', err);
return;
}
console.log('📊 文件大小:', stats.size, '字节');
// 7. 删除文件
fs.unlink(newFilePath, (err) => {
if (err) {
console.error('删除文件失败:', err);
return;
}
console.log('✅ 文件删除成功');
// 8. 删除文件夹
fs.rmdir(dataDir, { recursive: true }, (err) => {
if (err) {
console.error('删除文件夹失败:', err);
return;
}
console.log('✅ 文件夹删除成功');
console.log('🎉 全部操作完成!');
});
});
});
});
});
});
});
});
运行结果:
✅ 文件夹创建成功
✅ 文件写入成功
📄 文件内容:
姓名:张三
年龄:25
✅ 内容追加成功
✅ 文件重命名为 user-info.txt
📊 文件大小: 34 字节
✅ 文件删除成功
✅ 文件夹删除成功
🎉 全部操作完成!
提示 :上面的例子使用了回调嵌套,虽然能跑通,但代码可读性较差。在实际项目中,推荐使用
fs.promisesAPI 配合async/await来避免回调地狱。
10.2 进阶练习:批量重命名文件
上面的例子演示了单个文件的操作。实际开发中,我们经常需要批量处理 文件,比如给一批文件统一重命名。下面这个例子,会把 fs 文件夹中符合「数字.名称.js」格式的文件,统一补零为两位数编号:
javascript
const fs = require('fs');
// 读取 fs 文件夹下的所有文件
const files = fs.readdirSync('../fs');
files.forEach((item) => {
// 用 . 分割文件名,例如 "1.hello.js" -> ["1", "hello", "js"]
let data = item.split('.');
// 只处理符合"数字.名称.js"格式的文件
// 长度必须为 3,且第一个是数字
if (data.length !== 3 || !data[0] || isNaN(Number(data[0]))) {
return;
}
let [num, name, ext] = data;
// 如果编号小于 10,补零成两位数,如 1 -> 01
if (Number(num) < 10) {
num = '0' + num;
}
// 创建新的文件名
let newName = num + '.' + name + '.' + ext;
// 重命名
fs.renameSync(`../fs/${item}`, `../fs/${newName}`);
});
运行效果:
假设 fs 文件夹下原本有这些文件:
1.hello.js
2.world.js
10.node.js
运行后,1.hello.js 和 2.world.js 会被补零重命名:
01.hello.js
02.world.js
10.node.js
要点:
readdirSync同步读取文件夹,返回文件名数组。split('.')把文件名按.拆分成数组,再通过解构赋值let [num, name, ext] = data取出编号、名称和扩展名。isNaN(Number(data[0]))用来过滤掉不以数字开头的文件,避免误改。renameSync是同步重命名,适合这种批量小文件场景。- 这里使用了相对路径
../fs,实际项目中建议结合__dirname使用绝对路径,避免路径出错(见第九节)。
写一篇。
11. 总结
恭喜你!通过这篇文章,你已经掌握了 Node.js fs 模块的核心操作:
| 操作 | 异步方法 | 同步方法 | 流式方法 |
|---|---|---|---|
| 写入文件 | writeFile |
writeFileSync |
createWriteStream |
| 追加写入 | appendFile |
appendFileSync |