fs 模块完全指南:文件读写、移动删除与文件夹操作(新手友好)【Node.js】

目录

    • [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。下面分别介绍。

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+ 提供的新方法,推荐在新项目中使用。
对比项 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 进阶练习:文件管理小工具

让我们把今天学到的知识综合起来,写一个文件管理小工具,实现以下功能:

  1. 创建 data 文件夹
  2. 在 data 文件夹中写入一个 user.txt 文件
  3. 读取并打印文件内容
  4. 追加一行新内容
  5. 重命名文件为 user-info.txt
  6. 查看文件状态
  7. 删除文件
  8. 删除文件夹
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.promises API 配合 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
相关推荐
Json____38 分钟前
用 Node.js + Vue3 打造一个家居装修 AI 智能咨询助手
node.js·agent·网站开发·智能体·wwwoop.com
纪伊路上盛名在18 小时前
windows 配置vscode x11转发
ide·vscode·编辑器
Dumbness19 小时前
不发 npm 也能 npx:用 github: 协议分发一个零依赖 CLI
node.js
Patrick_Wilson1 天前
npm 的 bin 如何接到 npm run 与 npx
npm·node.js·前端工程化
hkj88081 天前
VsCode的使用
ide·vscode·编辑器
Patrick_Wilson1 天前
import 之后发生了什么:package.json、解析与安装产物
npm·node.js·前端工程化
Patrick_Wilson1 天前
npm 里 SemVer 和 dist-tag 如何决定装哪一版
npm·node.js·前端工程化
广东帝工2 天前
智慧航道:技术体系
编辑器
Dumbness2 天前
Node 22 下 node --test test/ 报 MODULE_NOT_FOUND:内置测试运行器的文件匹配规则
node.js·测试