跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

fs 模块(文件系统)

文章目录 什么是 fs 模块?

路径使用注意事项 一、文件写入:将数据持久化到磁盘 1. 异步写入:writeFile(回调风格) 2. 同步写入:writeFileSync(阻塞风格) 3. Promise 风格写入:fs.promises(现代异步) 4. 追加写入:appendFile(及同步 / Promise 版本) 5. 流式写入:createWriteStream(大文件场景) 二、文件读取:从磁盘加载数据到内存 1. 异步读取:readFile(回调风格) 2. 同步读取:readFileSync(阻塞风格) 3. Promise 风格读取 4. 流式读取:createReadStream(大文件场景) 三、文件移动与重命名:rename 四、文件删除:unlink /rm(推荐) 1. 现代方法:rm(支持更多选项) 五、文件夹操作:创建、读取与删除 1. 创建文件夹:mkdir(支持递归) 2. 读取文件夹:readdir(获取目录内容) 3. 删除文件夹:rm(支持递归删除非空目录) 六、查看资源状态:stat 七、实战场景案例 场景 1:日志切割(按日期拆分日志) 场景 2:大文件复制(避免内存溢出) 场景 3:目录批量处理(遍历并修改所有 txt 文件) 八、API 性能对比与选择指南 总结 在 Node.js 开发中,与本地文件系统交互是高频需求 —— 无论是读写配置文件、处理用户上传的文档,还是管理项目目录结构,都离不开

fs

模块。

本文将系统梳理

fs

模块的核心用法,从文件读写到目录管理,结合场景化示例与现代开发实践,帮你掌握文件系统操作的精髓。

什么是 fs 模块?

fs

(全称

file system

)是 Node.js 的 内置模块 ,无需额外安装即可使用。

它提供了一套完整的 API,用于操作计算机磁盘中的文件和目录,涵盖创建、读取、更新、删除(CRUD)等全流程。

路径使用注意事项 在文件操作中,路径的正确性直接影响程序稳定性,需特别注意: 相对路径 :以执行 Node 命令的 终端工作目录 为基准(而非代码文件所在目录),容易出现路径偏移(例如在不同目录执行同一脚本)。

绝对路径 :从磁盘根目录开始的完整路径(如

C:/project/file.txt

/user/project/file.txt

),推荐优先使用。

最佳实践:通过

path

模块的

resolve

方法结合

__dirname

(当前文件所在目录的绝对路径)生成绝对路径,例如:

const path = require("path");

const filePath = path.resolve(__dirname, "data.txt"); // 生成当前目录下data.txt的绝对路径

一、文件写入:将数据持久化到磁盘 文件写入是将内存中的数据保存到磁盘的过程,

fs

模块提供了 4 种常用方式,适用于不同场景。

同时支持 回调、同步、Promise 三种编码风格,可根据项目规范选择。

1. 异步写入:writeFile(回调风格) 特点 :非阻塞式操作,JavaScript 主线程不等待写入完成,适合不希望阻塞后续代码执行的场景。

语法:

fs.writeFile(file, data[, options], callback) file

:文件路径(推荐绝对路径)

data

:待写入的数据(字符串或 Buffer)

options

:可选配置(如编码格式

encoding: 'utf8'

flag: 'w'

默认覆盖,

flag: 'wx'

防止覆盖)

callback

:写入完成后的回调(参数

err

为错误信息,成功时为

null

) 示例:

const fs = require("fs");

const path = require("path"); const filePath = path.resolve(__dirname, "notes.txt");

// 异步写入(防止覆盖:若文件已存在则报错) fs.writeFile( filePath, "Hello Node.js", { encoding: "utf8", flag: "wx" }, (err) => { if (err) { if (err.code === "EEXIST") { // 捕获"文件已存在"错误 console.error("写入失败:文件已存在"); } else { console.error("写入失败:", err); } return; } console.log("写入成功!

"); } );

2. 同步写入:writeFileSync(阻塞风格) 特点 :阻塞式操作,主线程会等待写入完成后再执行后续代码,适合简单场景或脚本工具(避免回调嵌套)。

语法:

fs.writeFileSync(file, data[, options])

(无回调,直接通过 try/catch 捕获错误) 示例:

const fs = require("fs");

const path = require("path"); const filePath = path.resolve(__dirname, "notes.txt");

try { fs.writeFileSync(filePath, "同步写入内容", "utf8"); console.log("同步写入成功!

"); } catch (err) { if (err.code === "EACCES") { // 捕获"权限不足"错误 console.error("写入失败:无权限操作该文件"); } else { console.error("同步写入失败:", err); } }

3. Promise 风格写入:fs.promises(现代异步) Node.js 10+ 支持

fs.promises

模块,提供 Promise 风格 API,可配合

async/await

避免回调嵌套,更符合现代 JavaScript 开发习惯。

示例:

const fs = require("fs").promises;

const path = require("path");

async function writeFileDemo() { const filePath = path.resolve(__dirname, "notes.txt"); try { await fs.writeFile(filePath, "Promise风格的写入", "utf8"); console.log("写入成功"); } catch (err) { if (err.code === "ENOENT") { // 捕获"路径不存在"错误 console.error("写入失败:目标目录不存在"); } else { console.error("写入失败:", err); } } }

writeFileDemo();

4. 追加写入:appendFile(及同步 / Promise 版本) 特点 :在文件末尾添加内容(而非覆盖),常用于日志记录、数据累加等场景。

示例(Promise 风格):

const fs = require("fs").promises;

const path = require("path");

async function appendLog() { const logPath = path.resolve(__dirname, "app.log"); try { await fs.appendFile( logPath, `[${new Date().toISOString()}] 用户登录\n` ); console.log("日志追加成功"); } catch (err) { console.error("日志写入失败:", err); } }

appendLog();

5. 流式写入:createWriteStream(大文件场景) 特点 :分块写入数据,无需一次性加载全部内容到内存,适合 大文件(如 GB 级) 或 频繁写入 场景(如视频上传、大数据导出)。

语法:

fs.createWriteStream(path[, options])

(返回一个可写流对象) 示例:

const fs = require("fs");

const path = require("path");

// 创建流式写入对象(设置高水位线:每次缓冲区满1MB时写入磁盘) const ws = fs.createWriteStream( path.resolve(__dirname, "large-file.txt"), { highWaterMark: 1024 * 1024 } );

// 分块写入(模拟大文件内容) let i = 0; function write() { let ok = true;

while (i < 10000 && ok) { ok = ws.write(`这是第${i}行数据\n`); // 当缓冲区满时,ok为false i++; }

if (i === 10000) { ws.end("写入结束"); // 所有数据写入完成后关闭流 return; }

// 缓冲区清空后继续写入 ws.once("drain", write); }

write();

// 监听写入完成事件 ws.on("finish", () => { console.log("大文件写入完成!

"); });

核心优势:通过 “缓冲区” 机制减少磁盘 IO 次数,大幅提升大文件处理效率。

highWaterMark

参数可控制缓冲区大小(默认 64KB)。

二、文件读取:从磁盘加载数据到内存 文件读取是将磁盘中的数据加载到内存的过程,

fs

模块提供 3 种常用方式,需根据文件大小和场景选择。

1. 异步读取:readFile(回调风格) 特点 :非阻塞式读取,适合中小型文件(内容可一次性加载到内存)。

示例:

const fs = require("fs");

const path = require("path"); const filePath = path.resolve(__dirname, "notes.txt");

// 异步读取(指定编码为utf8,直接返回字符串) fs.readFile(filePath, "utf8", (err, data) => { if (err) { if (err.code === "ENOENT") { console.error("读取失败:文件不存在"); } else { console.error("读取失败:", err); } return; } console.log("文件内容:", data); });

2. 同步读取:readFileSync(阻塞风格) 示例:

const fs = require("fs");

const path = require("path"); const filePath = path.resolve(__dirname, "notes.txt");

try { const data = fs.readFileSync(filePath, "utf8"); console.log("同步读取内容:", data); } catch (err) { console.error("同步读取失败:", err); }

3. Promise 风格读取

const fs = require("fs").promises;

const path = require("path");

async function readFileDemo() { const filePath = path.resolve(__dirname, "notes.txt"); try { const data = await fs.readFile(filePath, "utf8"); console.log("Promise读取内容:", data); } catch (err) { console.error("读取失败:", err); } }

readFileDemo();

4. 流式读取:createReadStream(大文件场景) 特点 :分块读取数据,每次读取固定大小(默认 64KB),适合 大文件 (避免一次性占用过多内存)。

示例:

const fs = require("fs");

const path = require("path");

// 创建流式读取对象(设置每次读取128KB) const rs = fs.createReadStream( path.resolve(__dirname, "large-file.txt"), { highWaterMark: 128 * 1024 } );

// 监听数据块事件(每次读取到数据时触发) rs.on("data", (chunk) => { console.log(`读取到${chunk.length}字节数据`); // 处理数据块(如解析、转发等) // 若处理耗时,可暂停读取:rs.pause(),处理完后再rs.resume() });

// 监听读取完成事件 rs.on("end", () => { console.log("文件读取完毕!

"); });

// 监听错误事件 rs.on("error", (err) => { console.error("读取错误:", err); });

典型场景:视频播放、大日志文件分析、断点续传等。

三、文件移动与重命名:rename

fs.rename

(及同步 / Promise 版本)是一个 “多功能” API,既能重命名文件 / 目录,也能移动路径(跨目录、跨磁盘)。

示例(Promise 风格):

const fs = require("fs").promises;

const path = require("path");

async function moveFile() { // 1. 重命名文件 const oldName = path.resolve(__dirname, "notes.txt"); const newName = path.resolve(__dirname, "diary.txt");

try { await fs.rename(oldName, newName); console.log("重命名成功!

"); } catch (err) { console.error("重命名失败:", err); }

// 2. 移动文件到另一个目录(目标目录需存在) const sourcePath = path.resolve(__dirname, "diary.txt"); const targetPath = path.resolve(__dirname, "docs", "diary.txt");

try { await fs.rename(sourcePath, targetPath); console.log("移动成功!

"); } catch (err) { console.error("移动失败:", err); } }

moveFile();

注意:若目标路径已存在同名文件,会被 覆盖 ;移动跨磁盘文件时,可能触发复制 + 删除操作(性能较低)。

四、文件删除:unlink /rm(推荐) 删除文件可使用

fs.unlink

(传统方法)或

fs.rm

(Node.js 14.14 + 新增,更推荐)。

1. 现代方法:rm(支持更多选项)

const fs = require("fs").promises;

const path = require("path");

async function deleteFile() { // 删除文件(force: true 忽略不存在的文件,避免报错) try { await fs.rm(path.resolve(__dirname, "temp.txt"), { force: true }); console.log("文件已删除"); } catch (err) { console.error("删除失败:", err); } }

deleteFile();

五、文件夹操作:创建、读取与删除 目录(文件夹)操作是管理文件系统结构的基础,

fs

模块提供了专门的 API。

1. 创建文件夹:mkdir(支持递归) 特点 :支持递归创建多级目录(如

a/b/c

),需指定

recursive: true

示例:

const fs = require("fs").promises;

const path = require("path");

async function createDir() { // 递归创建多级目录 try { await fs.mkdir(path.resolve(__dirname, "data/logs/2024"), { recursive: true, }); console.log("多级目录创建成功"); } catch (err) { console.error("创建失败:", err); } }

createDir();

2. 读取文件夹:readdir(获取目录内容) 作用 :获取目录下的所有文件和子目录名称(默认仅名称,可通过选项获取详细信息)。

示例:

const fs = require("fs").promises;

const path = require("path");

async function readDir() { const dirPath = path.resolve(__dirname, "docs"); try { // 读取详细信息(包含是否为文件/目录) const items = await fs.readdir(dirPath, { withFileTypes: true }); items.forEach((item) => { if (item.isFile()) { console.log("文件:", item.name); } else if (item.isDirectory()) { console.log("目录:", item.name); } }); } catch (err) { console.error("读取失败:", err); } }

readDir();

3. 删除文件夹:rm(支持递归删除非空目录) Node.js 14.14 + 推荐使用

fs.rm

,支持

recursive: true

直接删除非空目录(无需手动删除内部文件)。

示例:

const fs = require("fs").promises;

const path = require("path");

async function deleteDir() { const dirPath = path.resolve(__dirname, "data"); try { // recursive: true 递归删除所有子内容,force: true 忽略不存在的目录 await fs.rm(dirPath, { recursive: true, force: true }); console.log("目录已删除"); } catch (err) { console.error("删除失败:", err); } }

deleteDir();

六、查看资源状态:stat

fs.stat

(及同步 / Promise 版本)用于获取文件或目录的详细信息(大小、创建时间、类型等)。

示例:

const fs = require("fs").promises;

const path = require("path");

async function getStats() { const targetPath = path.resolve(__dirname, "docs"); try { const stats = await fs.stat(targetPath); console.log("是否为文件:", stats.isFile()); // false(若targetPath是目录) console.log("是否为目录:", stats.isDirectory()); // true console.log("大小:", stats.size, "字节"); console.log("最后修改时间:", stats.mtime.toLocaleString()); } catch (err) { console.error("获取状态失败:", err); } }

getStats();

七、实战场景案例 将 API 与真实业务场景结合,理解 “何时用什么 API”: 场景 1:日志切割(按日期拆分日志) 需求:每天生成一个日志文件(如

2024-10-20.log

),避免单个日志过大。

实现思路:用

createWriteStream

结合定时任务,每天零点切换输出流。

const fs = require("fs");

const path = require("path"); const { format } = require("date-fns"); // 需安装date-fns:处理日期

let currentStream; let currentDate = format(new Date(), "yyyy-MM-dd");

// 获取当前日志流 function getLogStream() { const today = format(new Date(), "yyyy-MM-dd");

if (today !== currentDate || !currentStream) { // 切换日期或流未初始化时,创建新流 currentDate = today; const logPath = path.resolve(__dirname, `logs/${currentDate}.log`);

// 关闭旧流(若存在) if (currentStream) currentStream.close();

// 创建新流(追加模式) currentStream = fs.createWriteStream(logPath, { flags: "a" }); }

return currentStream; }

// 写入日志 function writeLog(content) { const stream = getLogStream(); stream.write(`[${new Date().toISOString()}] ${content}\n`); }

// 测试:每3秒写入一条日志 setInterval(() => { writeLog("用户访问了首页"); }, 3000);

场景 2:大文件复制(避免内存溢出) 需求:复制一个 1GB 的视频文件,避免一次性加载到内存。

实现思路:用可读流 + 可写流 “管道”(pipe)传输数据。

const fs = require("fs");

const path = require("path");

function copyLargeFile(source, target) { const rs = fs.createReadStream(source); const ws = fs.createWriteStream(target);

// 用pipe连接流:自动处理数据传输、缓冲区控制、错误处理 rs.pipe(ws);

// 监听完成事件 ws.on("finish", () => { console.log("文件复制完成"); });

// 监听错误事件 rs.on("error", (err) => console.error("读取错误:", err)); ws.on("error", (err) => console.error("写入错误:", err)); }

// 复制视频文件 copyLargeFile( path.resolve(__dirname, "source.mp4"), path.resolve(__dirname, "target.mp4") );

场景 3:目录批量处理(遍历并修改所有 txt 文件) 需求:递归遍历

docs

目录,将所有

.txt

文件中的 “旧内容” 替换为 “新内容”。

实现思路:结合

readdir

(递归遍历)+

stat

(判断文件类型)+

readFile

/

writeFile

(修改内容)。

const fs = require("fs").promises;

const path = require("path");

async function replaceInTxtFiles(dirPath, oldStr, newStr) { const items = await fs.readdir(dirPath, { withFileTypes: true });

for (const item of items) { const fullPath = path.resolve(dirPath, item.name);

if (item.isDirectory()) { // 递归处理子目录 await replaceInTxtFiles(fullPath, oldStr, newStr); } else if (item.isFile() && item.name.endsWith(".txt")) { // 处理txt文件 const content = await fs.readFile(fullPath, "utf8"); const newContent = content.replaceAll(oldStr, newStr); await fs.writeFile(fullPath, newContent, "utf8"); console.log(`已处理:${fullPath}`); } } }

// 批量替换 replaceInTxtFiles(path.resolve(__dirname, "docs"), "旧内容", "新内容") .then(() => console.log("所有文件处理完成")) .catch((err) => console.error("处理失败:", err));

八、API 性能对比与选择指南 操作场景推荐 API不推荐 API原因 小文件单次读写(<100KB)readFile/writeFile流式操作流式操作的初始化成本高于收益大文件读写(>10MB)流式操作readFile/writeFile易导致内存溢出(一次性加载)频繁追加(如日志)appendFile/ 流式写入writeFilewriteFile会覆盖内容,需额外处理脚本工具 / 简单场景同步 API(如readFileSync)异步 API同步代码更简洁,无回调嵌套现代项目 / 复杂流程fs.promises+async/await回调 API避免回调地狱,代码可读性更高 总结

fs

模块是 Node.js 操作文件系统的核心,掌握它能让你轻松处理本地文件交互。

关键总结: 路径安全 :始终使用

path.resolve(__dirname, '相对路径')

生成绝对路径,避免相对路径陷阱。

编码风格 :新手推荐

fs.promises + async/await

,兼顾可读性与非阻塞特性;简单脚本可用同步 API。

大文件处理 :必用流式操作(

createReadStream

/

createWriteStream

),通过

pipe

简化数据传输。

错误处理 :重点关注

ENOENT

(路径不存在)、

EEXIST

(文件已存在)、

EACCES

(权限不足)等常见错误,针对性处理。

实战技巧 :日志切割、大文件复制、目录批量处理等场景,需结合递归、流控制、定时任务等技术组合 API。

通过本文的梳理,应对大多数文件系统操作场景。

实际开发中,可根据具体需求灵活选择 API,平衡性能与开发效率。

相关文章