fs 操作文件系统
fs模块是NodeJS中用于操作文件系统的核心模块,它提供了文件的读写、目录操作、文件信息查询等功能。在服务端开发中,文件系统操作是非常常见的需求,比如读取配置文件、写入日志、处理上传文件等场景都需要用到fs模块。
模块导入
NodeJS中,fs支持同步读写文件和异步读写文件,不过一般来说我们都应该直接用异步方式,因为同步方式会阻塞事件循环,通常都是仅在程序启动等低并发场景下使用,适用场景很少。此外,由于一些历史遗留问题等原因,fs模块有多种导入方式,早期NodeJS中存在大量回调式API,现在则推荐使用Promise风格的API,因为它能够更好地配合async/await语法使用。
import fs from "node:fs/promises";
本篇笔记主要使用Promise风格的API进行介绍,这是目前推荐的异步文件操作方式。
文件读写操作
读取文件
使用fs.readFile()可以读取文件内容,这里注意如果以文本方式读取,需要指定正确的编码格式。
import fs from "node:fs/promises";
// 读取文件为Buffer
const buffer = await fs.readFile("./data.txt");
console.log(buffer); // <Buffer 48 65 6c 6c 6f ...>
// 读取文件为字符串
const content = await fs.readFile("./data.txt", "utf-8");
console.log(content); // Hello, NodeJS!
// 读取JSON文件
const jsonContent = await fs.readFile("./config.json", "utf-8");
const config = JSON.parse(jsonContent);
console.log(config);
写入文件
使用fs.writeFile()可以将内容写入文件,如果文件不存在会自动创建,如果文件已存在则会覆盖原有内容。
import fs from "node:fs/promises";
// 写入文本内容
await fs.writeFile("./output.txt", "Hello, NodeJS!", "utf-8");
// 写入JSON数据
const data = { name: "Tom", age: 18 };
await fs.writeFile("./data.json", JSON.stringify(data), "utf-8");
// 写入Buffer数据
const buffer = Buffer.from("Binary data");
await fs.writeFile("./binary.dat", buffer);
追加写入
如果需要在文件末尾追加内容而不是覆盖,可以使用fs.appendFile()。
import fs from "node:fs/promises";
// 追加文本内容
await fs.appendFile("./log.txt", "New log entry\n", "utf-8");
// 多次追加
await fs.appendFile("./log.txt", `[${new Date().toISOString()}] Info: Server started\n`);
await fs.appendFile("./log.txt", `[${new Date().toISOString()}] Info: Listening on port 3000\n`);
目录操作
创建目录
使用fs.mkdir()可以创建目录,通过recursive选项可以递归创建多层目录。
import fs from "node:fs/promises";
// 创建单层目录
await fs.mkdir("./new-folder");
// 递归创建多层目录
await fs.mkdir("./path/to/nested/folder", { recursive: true });
读取目录内容
使用fs.readdir()可以读取目录下的文件和子目录列表。
import fs from "node:fs/promises";
// 获取目录下的文件名列表
const files = await fs.readdir("./src");
console.log(files); // ['index.js', 'utils', 'components']
// 获取详细信息(包含文件类型)
const entries = await fs.readdir("./src", { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile()) {
console.log(`文件: ${entry.name}`);
} else if (entry.isDirectory()) {
console.log(`目录: ${entry.name}`);
}
}
删除目录
使用fs.rm()可以删除目录。
import fs from "node:fs/promises";
// 递归删除目录及其内容
await fs.rm("./folder-with-files", { recursive: true, force: true });
文件信息与状态
获取文件状态
使用fs.stat()可以获取文件或目录的详细信息,包括大小、创建时间、修改时间等。
import fs from "node:fs/promises";
const stats = await fs.stat("./data.txt");
console.log("是否为文件:", stats.isFile());
console.log("是否为目录:", stats.isDirectory());
console.log("文件大小(字节):", stats.size);
console.log("创建时间:", stats.birthtime);
console.log("最后修改时间:", stats.mtime);
console.log("最后访问时间:", stats.atime);
检查文件存在和权限
可以使用fs.access()检查文件是否存在以及是否具有特定权限。
import fs from "node:fs/promises";
import { constants } from "node:fs";
// 检查文件是否存在
async function fileExists(path) {
try {
await fs.access(path);
return true;
} catch {
return false;
}
}
const exists = await fileExists("./config.json");
console.log("文件存在:", exists);
// 检查文件是否可读可写
try {
await fs.access("./data.txt", constants.R_OK | constants.W_OK);
console.log("文件可读可写");
} catch {
console.log("文件权限不足");
}
文件操作
重命名和移动文件
使用fs.rename()可以重命名文件或将文件移动到其他位置。
import fs from "node:fs/promises";
// 重命名文件
await fs.rename("./old-name.txt", "./new-name.txt");
// 移动文件到其他目录
await fs.rename("./file.txt", "./backup/file.txt");
复制文件
使用fs.copyFile()可以复制文件。
import fs from "node:fs/promises";
// 复制文件
await fs.copyFile("./source.txt", "./destination.txt");
import fs from "node:fs/promises";
import { constants } from "node:fs";
// 仅在目标不存在时复制(避免覆盖)
await fs.copyFile("./source.txt", "./destination.txt", constants.COPYFILE_EXCL);
删除文件
使用fs.unlink()或fs.rm()可以删除文件。
import fs from "node:fs/promises";
// 删除文件
await fs.unlink("./temp.txt");
// 使用rm删除(更通用)
await fs.rm("./temp.txt");
流式操作
对于大文件的读写,使用流(Stream)可以避免一次性将整个文件加载到内存中,提高性能和内存效率,不过流式操作需要使用同步模块的API。
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
async function copyLargeFile(source, destination) {
try {
await pipeline(
createReadStream(source),
createWriteStream(destination)
);
console.log('文件复制成功');
} catch (err) {
console.error('复制失败:', err.message);
}
}
有关流的更多用法我们可以参考软件工程/NodeJS/NodeJS核心模块/stream-流章节。
监听文件变化
使用fs.watch()可以监听文件或目录的变化,这在开发工具中非常有用,比如实现热重载功能。
import fs from "node:fs/promises";
// 监听文件变化
const watcher = fs.watch("./src", { recursive: true });
for await (const event of watcher) {
console.log(`检测到变化: ${event.eventType} - ${event.filename}`);
}
错误处理
文件操作可能会抛出各种错误,常见的错误码包括:
ENOENT:文件或目录不存在EACCES:权限不足EEXIST:文件或目录已存在EISDIR:对目录执行了文件操作ENOTDIR:对文件执行了目录操作
import fs from "node:fs/promises";
async function safeReadFile(filePath) {
try {
return await fs.readFile(filePath, "utf-8");
} catch (err) {
switch (err.code) {
case "ENOENT":
console.error(`文件不存在: ${filePath}`);
break;
case "EACCES":
console.error(`没有权限读取文件: ${filePath}`);
break;
case "EISDIR":
console.error(`路径是一个目录而不是文件: ${filePath}`);
break;
default:
console.error(`读取文件时发生错误: ${err.message}`);
}
return null;
}
}
const content = await safeReadFile("./data.txt");
if (content !== null) {
console.log("文件内容:", content);
}