文件系统(fs 模块)
2026/7/13大约 5 分钟
文件系统(fs 模块)
fs(File System)模块是 Node.js 的核心模块之一,用于处理文件和目录的读写操作。它提供了同步(Sync)和异步(Callback/Promise)两种方式。
一、引入方式
// 回调方式(传统)
const fs = require('fs');
// Promise 方式(Node.js v14+)
const fs = require('fs/promises');
// 同时使用
const fs = require('fs');
const fsPromises = require('fs/promises');
二、读取文件
1. 异步读取(回调)
const fs = require('fs');
// 文本文件读取(默认 utf8)
fs.readFile('./hello.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
// 二进制文件读取(不传编码,返回 Buffer)
fs.readFile('./image.png', (err, buffer) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('Buffer 长度:', buffer.length);
});
2. 同步读取
const fs = require('fs');
try {
const data = fs.readFileSync('./config.json', 'utf8');
const config = JSON.parse(data);
console.log('配置:', config);
} catch (err) {
console.error('读取失败:', err);
}
3. Promise 方式读取
const fs = require('fs/promises');
async function readConfig() {
try {
const data = await fs.readFile('./config.json', 'utf8');
return JSON.parse(data);
} catch (err) {
console.error('读取失败:', err);
throw err;
}
}
readConfig().then(config => console.log(config));
三、写入文件
1. 覆盖写入
const fs = require('fs');
// 异步写入
fs.writeFile('./output.txt', 'Hello, Node.js!', 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('写入成功');
});
// 同步写入
fs.writeFileSync('./output.txt', 'Hello, Node.js!', 'utf8');
// Promise 方式
const fsPromises = require('fs/promises');
await fsPromises.writeFile('./output.txt', 'Hello, Node.js!', 'utf8');
2. 追加写入
const fs = require('fs');
const logLine = `[${new Date().toISOString()}] 用户登录\n`;
// 追加内容到文件末尾
fs.appendFile('./app.log', logLine, (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('日志已记录');
});
// 同步追加
fs.appendFileSync('./app.log', logLine);
3. 写入 JSON 文件
const fs = require('fs');
function saveJSON(filename, data) {
const json = JSON.stringify(data, null, 2);
fs.writeFileSync(filename, json, 'utf8');
}
function loadJSON(filename) {
const data = fs.readFileSync(filename, 'utf8');
return JSON.parse(data);
}
// 使用示例
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
];
saveJSON('./users.json', users);
const loaded = loadJSON('./users.json');
console.log(loaded);
四、文件与目录操作
1. 检查文件/目录是否存在
const fs = require('fs');
// 方式一:同步检查
if (fs.existsSync('./file.txt')) {
console.log('文件存在');
} else {
console.log('文件不存在');
}
// 方式二:通过 stat 获取详细信息
try {
const stats = fs.statSync('./file.txt');
console.log('是文件:', stats.isFile());
console.log('是目录:', stats.isDirectory());
console.log('文件大小:', stats.size, 'bytes');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
} catch (err) {
console.error('无法访问文件');
}
2. 创建目录
const fs = require('fs');
// 创建单级目录
fs.mkdirSync('./logs');
// 递归创建多级目录
fs.mkdirSync('./logs/2026/07', { recursive: true });
// 异步方式
fs.mkdir('./data', { recursive: true }, (err) => {
if (err) throw err;
console.log('目录创建成功');
});
3. 读取目录
const fs = require('fs');
// 读取目录下所有文件和子目录名
const files = fs.readdirSync('./');
console.log('当前目录内容:', files);
// 递归读取(需要自己实现)
function listFiles(dir, depth = 0) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = `${dir}/${item}`;
const stats = fs.statSync(fullPath);
const indent = ' '.repeat(depth);
if (stats.isDirectory()) {
console.log(`${indent}[目录] ${item}`);
listFiles(fullPath, depth + 1);
} else {
console.log(`${indent}[文件] ${item} (${stats.size} bytes)`);
}
}
}
listFiles('./src');
4. 重命名与移动
const fs = require('fs');
// 重命名
fs.renameSync('./old.txt', './new.txt');
// 移动文件
fs.renameSync('./source.txt', './backup/source.txt');
// 跨设备移动需要使用 copy + unlink
fs.copyFileSync('./source.txt', './backup/source.txt');
fs.unlinkSync('./source.txt');
5. 删除文件与目录
const fs = require('fs');
// 删除文件
fs.unlinkSync('./temp.txt');
// 删除空目录
fs.rmdirSync('./empty-dir');
// 递归删除(Node.js v14+)
fs.rmSync('./node_modules', { recursive: true, force: true });
// 异步方式
const fsPromises = require('fs/promises');
await fsPromises.rm('./temp', { recursive: true, force: true });
五、文件信息(stat)
const fs = require('fs');
const stats = fs.statSync('./file.txt');
console.log({
isFile: stats.isFile(), // 是否为文件
isDirectory: stats.isDirectory(), // 是否为目录
isSymbolicLink: stats.isSymbolicLink(), // 是否为符号链接
size: stats.size, // 字节数
birthtime: stats.birthtime, // 创建时间
mtime: stats.mtime, // 修改时间
atime: stats.atime, // 访问时间
ctime: stats.ctime, // 状态变更时间
mode: stats.mode.toString(8), // 权限(八进制)
});
六、监听文件变化
const fs = require('fs');
// 监听文件变化
fs.watch('./config.json', (eventType, filename) => {
console.log(`文件 ${filename} 发生事件:${eventType}`);
// eventType: 'change' 或 'rename'
});
// 监听目录变化
fs.watch('./logs', { recursive: true }, (eventType, filename) => {
console.log(`检测到变化:${filename}`);
});
七、文件复制
const fs = require('fs');
// 方式一:copyFile
fs.copyFileSync('./source.txt', './dest.txt');
// 方式二:使用流(适合大文件)
const readStream = fs.createReadStream('./big-file.zip');
const writeStream = fs.createWriteStream('./big-file-copy.zip');
readStream.pipe(writeStream);
writeStream.on('finish', () => {
console.log('大文件复制完成');
});
// 方式三:copyFile 带权限标志
const fsPromises = require('fs/promises');
await fsPromises.copyFile('./source.txt', './dest.txt',
fs.constants.COPYFILE_EXCL // 目标已存在时报错
);
八、文件路径处理辅助
const fs = require('fs');
const path = require('path');
// 安全的文件写入(确保目录存在)
function safeWriteFile(filePath, content) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, content, 'utf8');
}
safeWriteFile('./data/logs/2026/07/13.txt', '日志内容');
// 获取文件扩展名
function getExtension(filename) {
return path.extname(filename).toLowerCase();
}
console.log(getExtension('image.png')); // .png
console.log(getExtension('script.js')); // .js
// 批量重命名
function batchRename(dir, oldExt, newExt) {
const files = fs.readdirSync(dir);
for (const file of files) {
if (file.endsWith(oldExt)) {
const newName = file.replace(oldExt, newExt);
fs.renameSync(`${dir}/${file}`, `${dir}/${newName}`);
console.log(`${file} -> ${newName}`);
}
}
}
batchRename('./pages', '.htm', '.html');
九、同步 vs 异步选择指南
| 场景 | 推荐方式 |
|---|---|
| 服务器启动时读取配置 | 同步(简单) |
| 处理多个并发请求 | 异步/Promise |
| 命令行工具脚本 | 同步(方便) |
| 大文件处理 | 流(Stream) |
| 需要错误重试的复杂逻辑 | Promise |
// 同步适用场景:初始化时读取配置
const config = JSON.parse(fs.readFileSync('./config.json', 'utf8'));
// 异步适用场景:服务请求中处理文件
app.get('/file', async (req, res) => {
const data = await fsPromises.readFile('./data.txt', 'utf8');
res.send(data);
});
十、完整示例:简易文件管理器
const fs = require('fs');
const path = require('path');
class FileManager {
constructor(baseDir) {
this.baseDir = baseDir;
if (!fs.existsSync(baseDir)) {
fs.mkdirSync(baseDir, { recursive: true });
}
}
read(filename) {
const filePath = path.join(this.baseDir, filename);
if (!fs.existsSync(filePath)) {
throw new Error(`文件 ${filename} 不存在`);
}
return fs.readFileSync(filePath, 'utf8');
}
write(filename, content) {
const filePath = path.join(this.baseDir, filename);
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(filePath, content, 'utf8');
console.log(`已写入:${filePath}`);
}
delete(filename) {
const filePath = path.join(this.baseDir, filename);
fs.unlinkSync(filePath);
console.log(`已删除:${filePath}`);
}
list(subDir = '') {
const dirPath = path.join(this.baseDir, subDir);
const items = fs.readdirSync(dirPath);
return items.map(item => {
const fullPath = path.join(dirPath, item);
const stats = fs.statSync(fullPath);
return {
name: item,
type: stats.isFile() ? 'file' : 'dir',
size: stats.size,
modifiedAt: stats.mtime,
};
});
}
}
// 使用示例
const fm = new FileManager('./my-files');
fm.write('hello.txt', 'Hello, FileManager!');
fm.write('data/notes.txt', '这是一条笔记');
console.log(fm.list());
console.log(fm.read('hello.txt'));
