参考资源:
Node.js 提供了强大的文件系统(File System)模块,允许开发者与文件系统进行交互。fs 模块提供了同步和异步两种操作方式,支持文件读写、目录操作、文件监听、流式操作等功能。本文将深入探讨 Node.js 文件系统的各个方面,包括基础操作和高级特性。
一、文件系统基础
1.1 引入 fs 模块
在 Node.js 中,使用 require() 或 import 引入文件系统模块:
javascript
// CommonJS 方式
const fs = require('fs');
// ES Modules 方式
import fs from 'fs';
1.2 异步与同步操作
Node.js 文件系统提供了两种操作方式:
- 异步操作:非阻塞,使用回调函数处理结果(推荐)
- 同步操作:阻塞,直接返回结果(适用于脚本或初始化)
最佳实践: 在生产环境中优先使用异步操作,避免阻塞事件循环。
二、文件读写操作
2.1 读取文件
2.1.1 异步读取文件(readFile)
fs.readFile() 是异步读取文件的推荐方式:
javascript
const fs = require('fs');
// 基本用法
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log('文件内容:', data);
});
// 读取二进制文件(如图片)
fs.readFile('image.png', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件大小:', data.length, '字节');
});
参数说明:
path: 文件路径(字符串、Buffer 或 URL)encoding: 编码格式(可选,如 'utf8'、'ascii'、'base64')callback: 回调函数(err, data) => {}
2.1.2 同步读取文件(readFileSync)
fs.readFileSync() 是同步读取文件的方式:
javascript
const fs = require('fs');
try {
// 同步读取,会阻塞执行
const data = fs.readFileSync('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取文件失败:', err);
}
// 读取二进制文件
try {
const buffer = fs.readFileSync('image.png');
console.log('文件大小:', buffer.length, '字节');
} catch (err) {
console.error('读取失败:', err);
}
注意事项:
- 同步操作会阻塞 Node.js 事件循环
- 适合在应用启动时读取配置文件
- 不适合处理大文件或高并发场景
2.2 写入文件
2.2.1 异步写入文件(writeFile)
fs.writeFile() 用于异步写入文件:
javascript
const fs = require('fs');
// 写入文本文件
fs.writeFile('output.txt', 'Hello Node.js!', 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('文件写入成功');
});
// 写入 JSON 数据
const data = { name: 'Node.js', version: '18.0.0' };
fs.writeFile('data.json', JSON.stringify(data, null, 2), 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('JSON 文件写入成功');
});
// 追加内容(使用 flag: 'a')
fs.writeFile('log.txt', '新的一行\n', { flag: 'a', encoding: 'utf8' }, (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('内容追加成功');
});
常用 flag 选项:
'w': 写入(覆盖,默认)'a': 追加'r+': 读写'wx': 写入(如果文件不存在则失败)
2.2.2 同步写入文件(writeFileSync)
fs.writeFileSync() 用于同步写入文件:
javascript
const fs = require('fs');
try {
fs.writeFileSync('output.txt', 'Hello Node.js!', 'utf8');
console.log('文件写入成功');
} catch (err) {
console.error('写入失败:', err);
}
// 追加内容
try {
fs.writeFileSync('log.txt', '新的一行\n', { flag: 'a', encoding: 'utf8' });
console.log('内容追加成功');
} catch (err) {
console.error('追加失败:', err);
}
2.3 追加文件内容
除了使用 writeFile 的 flag: 'a' 选项,还可以使用专门的追加方法:
javascript
const fs = require('fs');
// 异步追加
fs.appendFile('log.txt', '新的日志条目\n', 'utf8', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('追加成功');
});
// 同步追加
try {
fs.appendFileSync('log.txt', '新的日志条目\n', 'utf8');
console.log('追加成功');
} catch (err) {
console.error('追加失败:', err);
}
三、目录操作
3.1 创建目录
3.1.1 异步创建目录(mkdir)
javascript
const fs = require('fs');
// 创建单个目录
fs.mkdir('newDir', (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('目录创建成功');
});
// 创建嵌套目录(需要 recursive: true)
fs.mkdir('path/to/nested/dir', { recursive: true }, (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('嵌套目录创建成功');
});
3.1.2 同步创建目录(mkdirSync)
javascript
const fs = require('fs');
try {
// 创建单个目录
fs.mkdirSync('newDir');
console.log('目录创建成功');
// 创建嵌套目录
fs.mkdirSync('path/to/nested/dir', { recursive: true });
console.log('嵌套目录创建成功');
} catch (err) {
console.error('创建目录失败:', err);
}
3.2 读取目录
3.2.1 异步读取目录(readdir)
fs.readdir() 用于读取目录内容:
javascript
const fs = require('fs');
const path = require('path');
// 基本用法
fs.readdir('./', (err, files) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
console.log('目录内容:', files);
});
// 读取详细信息(使用 withFileTypes)
fs.readdir('./', { withFileTypes: true }, (err, entries) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
entries.forEach(entry => {
if (entry.isDirectory()) {
console.log(`目录: ${entry.name}`);
} else if (entry.isFile()) {
console.log(`文件: ${entry.name}`);
}
});
});
// 递归读取目录
function readDirRecursive(dirPath) {
fs.readdir(dirPath, { withFileTypes: true }, (err, entries) => {
if (err) {
console.error('读取失败:', err);
return;
}
entries.forEach(entry => {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
console.log(`目录: ${fullPath}`);
readDirRecursive(fullPath);
} else {
console.log(`文件: ${fullPath}`);
}
});
});
}
readDirRecursive('./');
3.2.2 同步读取目录(readdirSync)
javascript
const fs = require('fs');
try {
const files = fs.readdirSync('./');
console.log('目录内容:', files);
// 读取详细信息
const entries = fs.readdirSync('./', { withFileTypes: true });
entries.forEach(entry => {
console.log(entry.isDirectory() ? `目录: ${entry.name}` : `文件: ${entry.name}`);
});
} catch (err) {
console.error('读取目录失败:', err);
}
3.3 删除目录
3.3.1 异步删除目录(rmdir)
javascript
const fs = require('fs');
// 删除空目录
fs.rmdir('emptyDir', (err) => {
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录删除成功');
});
// 删除非空目录(需要 recursive: true)
fs.rmdir('nonEmptyDir', { recursive: true }, (err) => {
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录及其内容删除成功');
});
注意: 在 Node.js 14.14.0+ 版本中,推荐使用 fs.rm() 替代 fs.rmdir() 删除非空目录。
3.3.2 同步删除目录(rmdirSync)
javascript
const fs = require('fs');
try {
// 删除空目录
fs.rmdirSync('emptyDir');
console.log('目录删除成功');
// 删除非空目录
fs.rmdirSync('nonEmptyDir', { recursive: true });
console.log('目录及其内容删除成功');
} catch (err) {
console.error('删除目录失败:', err);
}
3.4 删除文件
javascript
const fs = require('fs');
// 异步删除文件
fs.unlink('file.txt', (err) => {
if (err) {
console.error('删除文件失败:', err);
return;
}
console.log('文件删除成功');
});
// 同步删除文件
try {
fs.unlinkSync('file.txt');
console.log('文件删除成功');
} catch (err) {
console.error('删除文件失败:', err);
}
四、文件系统高级操作
4.1 文件监听
4.1.1 使用 watch 监听文件变化
fs.watch() 是跨平台的文件监听 API,但行为可能因平台而异:
javascript
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() 使用轮询机制,性能较差,不推荐使用:
javascript
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 流式文件操作
流(Stream)是处理大文件的高效方式,避免一次性将整个文件加载到内存中。
4.2.1 创建可读流
javascript
const fs = require('fs');
// 创建可读流
const readStream = fs.createReadStream('large-file.txt', 'utf8');
readStream.on('data', (chunk) => {
console.log('读取数据块:', chunk.length, '字节');
// 处理数据块
});
readStream.on('end', () => {
console.log('文件读取完成');
});
readStream.on('error', (err) => {
console.error('读取错误:', err);
});
4.2.2 创建可写流
javascript
const fs = require('fs');
// 创建可写流
const writeStream = fs.createWriteStream('output.txt', 'utf8');
writeStream.write('第一行数据\n');
writeStream.write('第二行数据\n');
writeStream.write('第三行数据\n');
writeStream.end(); // 结束写入
writeStream.on('finish', () => {
console.log('文件写入完成');
});
writeStream.on('error', (err) => {
console.error('写入错误:', err);
});
4.2.3 使用管道(pipe)复制文件
javascript
const fs = require('fs');
// 使用管道复制大文件
const readStream = fs.createReadStream('source.txt');
const writeStream = fs.createWriteStream('destination.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
console.log('文件复制完成');
});
writeStream.on('error', (err) => {
console.error('复制错误:', err);
});
4.2.4 流式处理大文件示例
javascript
const fs = require('fs');
const readline = require('readline');
// 逐行读取大文件
function processLargeFile(filePath) {
const readStream = fs.createReadStream(filePath, 'utf8');
const rl = readline.createInterface({
input: readStream,
crlfDelay: Infinity
});
rl.on('line', (line) => {
// 处理每一行
console.log('处理行:', line);
});
rl.on('close', () => {
console.log('文件处理完成');
});
}
processLargeFile('large-file.txt');
4.3 文件权限
4.3.1 检查文件权限
javascript
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);
}
权限常量:
fs.constants.F_OK: 文件存在fs.constants.R_OK: 可读fs.constants.W_OK: 可写fs.constants.X_OK: 可执行
4.3.2 修改文件权限
javascript
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.4 文件统计信息
4.4.1 获取文件统计信息(stat)
javascript
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);
}
Stats 对象属性:
size: 文件大小(字节)birthtime: 创建时间mtime: 修改时间atime: 访问时间ctime: 状态变更时间isFile(): 是否为文件isDirectory(): 是否为目录isSymbolicLink(): 是否为符号链接
4.4.2 检查文件或目录是否存在
javascript
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 文件复制工具
javascript
const fs = require('fs');
function copyFile(source, destination) {
return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
readStream.on('error', reject);
writeStream.on('error', reject);
writeStream.on('finish', resolve);
readStream.pipe(writeStream);
});
}
// 使用示例
copyFile('source.txt', 'destination.txt')
.then(() => console.log('复制成功'))
.catch(err => console.error('复制失败:', err));
5.2 目录遍历工具
javascript
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.3 日志记录工具
javascript
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 错误处理
始终处理文件系统操作的错误:
javascript
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 模块处理路径,确保跨平台兼容:
javascript
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'data', 'file.txt');
fs.readFile(filePath, 'utf8', (err, data) => {
// ...
});
6.3 性能优化
- 对于大文件,使用流(Stream)而不是
readFile/writeFile - 优先使用异步操作,避免阻塞事件循环
- 使用
fs.promisesAPI 或util.promisify将回调转换为 Promise
javascript
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:
javascript
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
关键要点:
- 优先使用异步操作,避免阻塞事件循环
- 处理大文件时使用流(Stream)
- 始终进行错误处理
- 使用
path模块处理路径,确保跨平台兼容 - 考虑使用
fs.promisesAPI 或 Promise 包装器