参考资源:
Node.js 提供了强大的文件系统(File System)模块,允许开发者与文件系统进行交互。fs 模块提供了同步和异步两种操作方式,支持文件读写、目录操作、文件监听等功能。本文将深入探讨 Node.js 文件系统的各个方面,包括基础操作和高级特性。
一、文件系统基础
1.1 引入 fs 模块
在 Node.js 中,使用 require() 或 import 引入文件系统模块:
// CommonJS 方式
const fs = require('fs');
// ES Modules 方式
import fs from 'fs';
1.2 异步与同步操作
Node.js 文件系统提供了两种操作方式:
- 异步操作:非阻塞,使用回调函数处理结果(推荐)
- 同步操作:阻塞,直接返回结果(适用于脚本或初始化)
最佳实践: 在生产环境中优先使用异步操作,避免阻塞事件循环。
二、文件读写操作
2.1 读取文件
API 语法:
fs.readFileSync(path[, options])- 同步读取fs.readFile(path[, options], callback)- 异步读取fsPromises.readFile(path[, options])- Promise 方式
参数说明:
path: 文件路径(字符串、Buffer 或 URL)options: 选项对象或字符串编码encoding: 编码格式(如 'utf8'、'ascii'、'base64'),默认 null(返回 Buffer)flag: 文件标志,默认 'r'signal: AbortSignal,用于取消操作
callback(err, data): 回调函数,data为文件内容
常用 flag 选项:
'r': 只读(默认),文件不存在则报错'r+': 读写,文件不存在则报错'rs+': 同步读写,绕过操作系统缓存'w': 写入(覆盖),文件不存在则创建'wx': 写入(排他),文件存在则失败'a': 追加,文件不存在则创建'ax': 追加(排他),文件存在则失败
返回值:
- 同步:返回文件内容(字符串或 Buffer)
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为文件内容
2.1.1 同步操作
const fs = require('fs');
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
2.1.2 异步回调
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
2.1.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function readFile() {
try {
const data = await fsPromises.readFile('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
}
2.2 写入文件
API 语法:
fs.writeFileSync(file, data[, options])- 同步写入fs.writeFile(file, data[, options], callback)- 异步写入fsPromises.writeFile(file, data[, options])- Promise 方式
参数说明:
file: 文件路径(字符串、Buffer 或 URL)data: 要写入的数据(字符串、Buffer、TypedArray、DataView)options: 选项对象或字符串编码encoding: 编码格式,默认 'utf8'mode: 文件权限,默认 0o666flag: 文件标志,默认 'w'signal: AbortSignal,用于取消操作
callback(err): 回调函数
常用 flag 选项:
'w': 写入(覆盖,默认)'a': 追加'r+': 读写'wx': 写入(如果文件不存在则失败)
返回值:
- 同步:无返回值
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为 undefined
2.2.1 同步操作
const fs = require('fs');
try {
fs.writeFileSync('output.txt', 'Hello Node.js!', 'utf8');
console.log('写入成功');
} catch (err) {
console.error('写入失败:', err);
}
2.2.2 异步回调
const fs = require('fs');
fs.writeFile('output.txt', 'Hello Node.js!', 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('写入成功');
});
2.2.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function writeFile() {
try {
await fsPromises.writeFile('output.txt', 'Hello Node.js!', 'utf8');
console.log('写入成功');
} catch (err) {
console.error('写入失败:', err);
}
}
2.3 追加文件内容
API 语法:
fs.appendFileSync(path, data[, options])- 同步追加fs.appendFile(path, data[, options], callback)- 异步追加fsPromises.appendFile(path, data[, options])- Promise 方式
参数说明:
path: 文件路径(字符串、Buffer 或 URL)data: 要追加的数据(字符串、Buffer)options: 选项对象或字符串编码encoding: 编码格式,默认 'utf8'mode: 文件权限,默认 0o666flag: 文件标志,默认 'a'
callback(err): 回调函数
返回值:
- 同步:无返回值
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为 undefined
2.3.1 同步操作
const fs = require('fs');
try {
fs.appendFileSync('log.txt', '新的日志条目\n', 'utf8');
console.log('追加成功');
} catch (err) {
console.error('追加失败:', err);
}
2.3.2 异步回调
const fs = require('fs');
fs.appendFile('log.txt', '新的日志条目\n', 'utf8', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('追加成功');
});
2.3.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function appendFile() {
try {
await fsPromises.appendFile('log.txt', '新的日志条目\n', 'utf8');
console.log('追加成功');
} catch (err) {
console.error('追加失败:', err);
}
}
三、目录操作
3.1 创建目录
API 语法:
fs.mkdirSync(path[, options])- 同步创建fs.mkdir(path[, options], callback)- 异步创建fsPromises.mkdir(path[, options])- Promise 方式
参数说明:
path: 目录路径(字符串、Buffer 或 URL)options: 选项对象recursive: 是否递归创建,默认 falsemode: 目录权限,默认 0o777
callback(err[, path]): 回调函数,path为创建的目录路径
返回值:
- 同步:返回创建的目录路径(如果 recursive 为 true)
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为创建的目录路径
3.1.1 同步操作
const fs = require('fs');
try {
fs.mkdirSync('newDir');
fs.mkdirSync('path/to/nested/dir', { recursive: true });
console.log('目录创建成功');
} catch (err) {
console.error('创建失败:', err);
}
3.1.2 异步回调
const fs = require('fs');
fs.mkdir('newDir', (err) => {
if (err) {
console.error('创建失败:', err);
return;
}
console.log('目录创建成功');
});
3.1.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function createDir() {
try {
await fsPromises.mkdir('newDir', { recursive: true });
console.log('目录创建成功');
} catch (err) {
console.error('创建失败:', err);
}
}
3.2 读取目录
API 语法:
fs.readdirSync(path[, options])- 同步读取fs.readdir(path[, options], callback)- 异步读取fsPromises.readdir(path[, options])- Promise 方式
参数说明:
path: 目录路径(字符串、Buffer 或 URL)options: 选项对象或字符串编码encoding: 编码格式,默认 'utf8'withFileTypes: 是否返回 fs.Dirent 对象,默认 falserecursive: 是否递归读取(Node.js 20+),默认 false
callback(err, files): 回调函数,files为文件/目录名数组
返回值:
- 同步:返回文件/目录名数组或 fs.Dirent 对象数组
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为文件/目录名数组
3.2.1 同步操作
const fs = require('fs');
try {
const files = fs.readdirSync('./');
console.log('目录内容:', files);
} catch (err) {
console.error('读取失败:', err);
}
3.2.2 异步回调
const fs = require('fs');
fs.readdir('./', (err, files) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('目录内容:', files);
});
3.2.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function readDir() {
try {
const files = await fsPromises.readdir('./');
console.log('目录内容:', files);
} catch (err) {
console.error('读取失败:', err);
}
}
3.3 删除目录
API 语法:
fs.rmdirSync(path[, options])- 同步删除(空目录)fs.rmSync(path[, options])- 同步删除(推荐,Node.js 14.14.0+)fs.rmdir(path[, options], callback)- 异步删除(空目录)fs.rm(path[, options], callback)- 异步删除(推荐)fsPromises.rmdir(path[, options])- Promise 方式(空目录)fsPromises.rm(path[, options])- Promise 方式(推荐)
参数说明:
path: 目录路径(字符串、Buffer 或 URL)options: 选项对象recursive: 是否递归删除,默认 falseforce: 是否强制删除(忽略错误),默认 falsemaxRetries: 最大重试次数,默认 0retryDelay: 重试延迟(毫秒),默认 100
callback(err): 回调函数
注意: Node.js 14.14.0+ 推荐使用 fs.rm() 替代 fs.rmdir() 删除非空目录。
返回值:
- 同步:无返回值
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为 undefined
3.3.1 同步操作
const fs = require('fs');
try {
fs.rmdirSync('emptyDir');
fs.rmSync('nonEmptyDir', { recursive: true });
console.log('删除成功');
} catch (err) {
console.error('删除失败:', err);
}
3.3.2 异步回调
const fs = require('fs');
fs.rmdir('emptyDir', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('删除成功');
});
3.3.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function removeDir() {
try {
await fsPromises.rm('nonEmptyDir', { recursive: true });
console.log('删除成功');
} catch (err) {
console.error('删除失败:', err);
}
}
3.4 删除文件
API 语法:
fs.unlinkSync(path)- 同步删除fs.unlink(path, callback)- 异步删除fsPromises.unlink(path)- Promise 方式
参数说明:
path: 文件路径(字符串、Buffer 或 URL)callback(err): 回调函数
返回值:
- 同步:无返回值
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为 undefined
3.4.1 同步操作
const fs = require('fs');
try {
fs.unlinkSync('file.txt');
console.log('删除成功');
} catch (err) {
console.error('删除失败:', err);
}
3.4.2 异步回调
const fs = require('fs');
fs.unlink('file.txt', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('删除成功');
});
3.4.3 Promise/async-await(推荐)
const fsPromises = require('fs').promises;
async function removeFile() {
try {
await fsPromises.unlink('file.txt');
console.log('删除成功');
} catch (err) {
console.error('删除失败:', err);
}
}
四、文件系统高级操作
4.1 文件监听
API 语法:
fs.watch(filename[, options][, listener])- 监听文件/目录变化
参数说明:
filename: 文件或目录路径(字符串、Buffer 或 URL)options: 选项对象persistent: 是否持续监听,默认 truerecursive: 是否递归监听子目录(仅限 Windows、macOS),默认 falseencoding: 文件名编码,默认 'utf8'
listener(eventType, filename): 监听器函数eventType: 事件类型('rename' 或 'change')filename: 文件名(可能为 null)
返回值:
- 返回
fs.FSWatcher对象,可调用.close()关闭监听
事件类型:
'rename': 文件或目录被重命名或删除'change': 文件内容发生变化
4.1.1 使用 watch 监听文件变化
const fs = require('fs');
// 监听文件
const watcher = fs.watch('example.txt', (eventType, filename) => {
if (filename) {
console.log(`事件类型: ${eventType}`);
console.log(`文件名: ${filename}`);
if (eventType === 'change') {
console.log('文件内容发生变化');
// 读取最新内容
fs.readFile('example.txt', 'utf8', (err, data) => {
if (!err) {
console.log('最新内容:', data);
}
});
}
}
});
// 监听目录
const dirWatcher = fs.watch('./', { recursive: true }, (eventType, filename) => {
console.log(`事件类型: ${eventType}, 文件: ${filename}`);
});
// 关闭监听器
setTimeout(() => {
watcher.close();
dirWatcher.close();
console.log('监听器已关闭');
}, 60000); // 60秒后关闭
事件类型:
'rename': 文件或目录被重命名或删除'change': 文件内容发生变化
4.1.2 使用 watchFile 监听文件(已弃用)
fs.watchFile() 使用轮询机制,性能较差,不推荐使用:
const fs = require('fs');
// 监听文件变化(轮询方式)
fs.watchFile('example.txt', { interval: 1000 }, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
console.log('文件已修改');
console.log('当前修改时间:', curr.mtime);
console.log('之前修改时间:', prev.mtime);
}
});
// 停止监听
setTimeout(() => {
fs.unwatchFile('example.txt');
console.log('已停止监听');
}, 30000);
注意: fs.watchFile() 已被标记为弃用,推荐使用 fs.watch()。
4.2 文件权限
API 语法:
fs.accessSync(path[, mode])- 同步检查权限fs.access(path[, mode], callback)- 异步检查权限fsPromises.access(path[, mode])- Promise 方式fs.chmodSync(path, mode)- 同步修改权限fs.chmod(path, mode, callback)- 异步修改权限fsPromises.chmod(path, mode)- Promise 方式
参数说明:
path: 文件路径(字符串、Buffer 或 URL)mode: 权限模式(access)或权限值(chmod)fs.constants.F_OK: 文件存在fs.constants.R_OK: 可读fs.constants.W_OK: 可写fs.constants.X_OK: 可执行- chmod 使用八进制数,如
0o755、0o644
callback(err): 回调函数
权限常量:
fs.constants.F_OK: 文件存在fs.constants.R_OK: 可读fs.constants.W_OK: 可写fs.constants.X_OK: 可执行
返回值:
- access 同步:无返回值(抛出异常表示无权限)
- access 异步回调:无返回值
- access Promise:返回 Promise,resolve 值为 undefined
- chmod:无返回值
4.3.1 检查文件权限
const fs = require('fs');
// 检查文件访问权限(异步)
fs.access('file.txt', fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK, (err) => {
if (err) {
console.error('文件不可访问:', err);
return;
}
console.log('文件可读可写');
});
// 检查文件权限(同步)
try {
fs.accessSync('file.txt', fs.constants.R_OK);
console.log('文件可读');
} catch (err) {
console.error('文件不可读:', err);
}
4.3.2 修改文件权限
const fs = require('fs');
// 异步修改权限
fs.chmod('file.txt', 0o755, (err) => {
if (err) {
console.error('修改权限失败:', err);
return;
}
console.log('权限修改成功');
});
// 同步修改权限
try {
fs.chmodSync('file.txt', 0o644);
console.log('权限修改成功');
} catch (err) {
console.error('修改权限失败:', err);
}
权限模式说明:
0o755: 所有者可读可写可执行,组和其他用户可读可执行0o644: 所有者可读可写,组和其他用户只读
4.3 文件统计信息
API 语法:
fs.statSync(path[, options])- 同步获取统计信息fs.stat(path[, options], callback)- 异步获取统计信息fsPromises.stat(path[, options])- Promise 方式fs.lstatSync(path[, options])- 同步获取链接统计信息fs.lstat(path[, options], callback)- 异步获取链接统计信息fsPromises.lstat(path[, options])- Promise 方式
参数说明:
path: 文件路径(字符串、Buffer 或 URL)options: 选项对象bigint: 是否返回 BigInt 类型的时间戳,默认 false
callback(err, stats): 回调函数,stats为 fs.Stats 对象
Stats 对象属性:
size: 文件大小(字节)birthtime: 创建时间mtime: 修改时间atime: 访问时间ctime: 状态变更时间isFile(): 是否为文件isDirectory(): 是否为目录isSymbolicLink(): 是否为符号链接
返回值:
- 同步:返回 fs.Stats 对象
- 异步回调:无返回值
- Promise:返回 Promise,resolve 值为 fs.Stats 对象
4.3.1 获取文件统计信息(stat)
const fs = require('fs');
// 异步获取文件统计信息
fs.stat('file.txt', (err, stats) => {
if (err) {
console.error('获取统计信息失败:', err);
return;
}
console.log('文件统计信息:');
console.log('是否为文件:', stats.isFile());
console.log('是否为目录:', stats.isDirectory());
console.log('文件大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
console.log('访问时间:', stats.atime);
console.log('权限:', stats.mode.toString(8));
});
// 同步获取文件统计信息
try {
const stats = fs.statSync('file.txt');
console.log('文件大小:', stats.size, '字节');
console.log('修改时间:', stats.mtime);
} catch (err) {
console.error('获取统计信息失败:', err);
}
4.3.2 检查文件或目录是否存在
const fs = require('fs');
// 异步检查
fs.access('file.txt', fs.constants.F_OK, (err) => {
if (err) {
console.log('文件不存在');
} else {
console.log('文件存在');
}
});
// 同步检查(推荐)
function fileExists(filePath) {
try {
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch {
return false;
}
}
if (fileExists('file.txt')) {
console.log('文件存在');
} else {
console.log('文件不存在');
}
五、实用示例
5.1 目录遍历工具
const fs = require('fs');
const path = require('path');
function walkDir(dirPath, callback) {
fs.readdir(dirPath, { withFileTypes: true }, (err, entries) => {
if (err) {
callback(err);
return;
}
entries.forEach(entry => {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
callback(null, fullPath, 'directory');
walkDir(fullPath, callback);
} else {
callback(null, fullPath, 'file');
}
});
});
}
// 使用示例
walkDir('./', (err, filePath, type) => {
if (err) {
console.error('遍历错误:', err);
return;
}
console.log(`${type}: ${filePath}`);
});
5.2 日志记录工具
const fs = require('fs');
const path = require('path');
class Logger {
constructor(logFile) {
this.logFile = logFile;
this.ensureLogFile();
}
ensureLogFile() {
if (!fs.existsSync(this.logFile)) {
fs.writeFileSync(this.logFile, '');
}
}
log(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
fs.appendFile(this.logFile, logMessage, 'utf8', (err) => {
if (err) {
console.error('日志写入失败:', err);
}
});
}
}
// 使用示例
const logger = new Logger('app.log');
logger.log('应用程序启动');
logger.log('处理用户请求');
六、最佳实践
6.1 错误处理
始终处理文件系统操作的错误:
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else if (err.code === 'EACCES') {
console.error('权限不足');
} else {
console.error('未知错误:', err);
}
return;
}
// 处理数据
});
6.2 路径处理
使用 path 模块处理路径,确保跨平台兼容:
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'data', 'file.txt');
fs.readFile(filePath, 'utf8', (err, data) => {
// ...
});
6.3 性能优化
- 优先使用异步操作,避免阻塞事件循环
- 使用
fs.promisesAPI 或util.promisify将回调转换为 Promise
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
// 使用 async/await
async function readFileAsync() {
try {
const data = await readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('读取失败:', err);
}
}
6.4 使用 Promise API
Node.js 10+ 提供了 fs.promises API:
const fs = require('fs').promises;
async function fileOperations() {
try {
// 读取文件
const data = await fs.readFile('file.txt', 'utf8');
console.log('文件内容:', data);
// 写入文件
await fs.writeFile('output.txt', 'Hello World', 'utf8');
// 创建目录
await fs.mkdir('newDir', { recursive: true });
// 读取目录
const files = await fs.readdir('./');
console.log('目录内容:', files);
} catch (err) {
console.error('操作失败:', err);
}
}
fileOperations();
七、总结
Node.js 文件系统模块提供了丰富的功能,包括:
- 基础操作:文件读写、目录操作
- 高级特性:文件监听、权限管理、统计信息
- 多种方式:同步/异步操作、回调/Promise/async-await
关键要点:
- 优先使用异步操作,避免阻塞事件循环
- 始终进行错误处理
- 使用
path模块处理路径,确保跨平台兼容 - 考虑使用
fs.promisesAPI 或 Promise 包装器