Node.js-文件系统模块
·
Node.js 文件系统模块 (fs)
1. 概述
Node.js 的 fs 模块提供了文件系统操作的 API,可以进行文件的读取、写入、删除、重命名等操作。
1.1 导入方式
// CommonJS 方式
const fs = require('fs');
// ES6 模块方式
import fs from 'fs';
// 使用 Promise API
const fs = require('fs').promises;
// 或
import fs from 'fs/promises';
2. 核心概念
2.1 同步 vs 异步
- 异步方法: 非阻塞,使用回调函数或 Promise
- 同步方法: 阻塞式,方法名后缀为
Sync - Promise API: 返回 Promise 对象,支持 async/await
// 异步(回调)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// 同步
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
// Promise
fs.promises.readFile('file.txt', 'utf8')
.then(data => console.log(data))
.catch(err => console.error(err));
// async/await
async function readFileAsync() {
try {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
3. 常用方法
3.1 文件读取
readFile - 读取整个文件
const fs = require('fs');
// 读取文本文件
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log('文件内容:', data);
});
// 读取二进制文件
fs.readFile('image.png', (err, data) => {
if (err) throw err;
console.log('Buffer:', data);
});
createReadStream - 流式读取
const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 64 * 1024 // 64KB 缓冲区
});
readStream.on('data', (chunk) => {
console.log('读取到数据块:', chunk.length, '字节');
});
readStream.on('end', () => {
console.log('文件读取完成');
});
readStream.on('error', (err) => {
console.error('读取错误:', err);
});
3.2 文件写入
writeFile - 写入文件(覆盖)
const fs = require('fs');
const content = '这是要写入的内容';
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('文件写入成功');
});
appendFile - 追加内容
fs.appendFile('log.txt', '新的日志记录\n', 'utf8', (err) => {
if (err) throw err;
console.log('内容已追加');
});
createWriteStream - 流式写入
const writeStream = fs.createWriteStream('output.txt', {
encoding: 'utf8'
});
writeStream.write('第一行\n');
writeStream.write('第二行\n');
writeStream.end('最后一行\n');
writeStream.on('finish', () => {
console.log('写入完成');
});
3.3 文件操作
unlink - 删除文件
fs.unlink('file-to-delete.txt', (err) => {
if (err) {
console.error('删除失败:', err);
return;
}
console.log('文件已删除');
});
rename - 重命名/移动文件
fs.rename('old-name.txt', 'new-name.txt', (err) => {
if (err) throw err;
console.log('文件已重命名');
});
// 也可用于移动文件
fs.rename('file.txt', './backup/file.txt', (err) => {
if (err) throw err;
console.log('文件已移动');
});
copyFile - 复制文件
fs.copyFile('source.txt', 'destination.txt', (err) => {
if (err) throw err;
console.log('文件已复制');
});
// 使用标志防止覆盖
fs.copyFile('source.txt', 'dest.txt', fs.constants.COPYFILE_EXCL, (err) => {
if (err) {
console.log('目标文件已存在');
return;
}
console.log('复制成功');
});
stat - 获取文件信息
fs.stat('file.txt', (err, stats) => {
if (err) throw err;
console.log('是否为文件:', stats.isFile());
console.log('是否为目录:', stats.isDirectory());
console.log('文件大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
});
3.4 目录操作
mkdir - 创建目录
// 创建单个目录
fs.mkdir('new-folder', (err) => {
if (err) throw err;
console.log('目录已创建');
});
// 递归创建嵌套目录
fs.mkdir('path/to/nested/folder', { recursive: true }, (err) => {
if (err) throw err;
console.log('嵌套目录已创建');
});
readdir - 读取目录
fs.readdir('./my-folder', (err, files) => {
if (err) throw err;
console.log('目录内容:', files);
// ['file1.txt', 'file2.js', 'subfolder']
});
// 获取详细信息
fs.readdir('./my-folder', { withFileTypes: true }, (err, entries) => {
if (err) throw err;
entries.forEach(entry => {
console.log(entry.name, entry.isDirectory() ? '(目录)' : '(文件)');
});
});
rmdir - 删除目录
// 删除空目录
fs.rmdir('empty-folder', (err) => {
if (err) throw err;
console.log('目录已删除');
});
// 递归删除非空目录(Node.js 14.14+)
fs.rm('folder-with-files', { recursive: true, force: true }, (err) => {
if (err) throw err;
console.log('目录及其内容已删除');
});
3.5 文件监听
watch - 监听文件变化
fs.watch('config.json', (eventType, filename) => {
console.log('事件类型:', eventType); // 'rename' 或 'change'
console.log('文件名:', filename);
if (eventType === 'change') {
console.log('文件已修改');
}
});
watchFile - 轮询监听
fs.watchFile('data.txt', { interval: 1000 }, (curr, prev) => {
console.log('当前修改时间:', curr.mtime);
console.log('之前修改时间:', prev.mtime);
if (curr.mtime !== prev.mtime) {
console.log('文件已更新');
}
});
4. 实战案例
案例 1: 文件复制工具
const fs = require('fs');
const path = require('path');
function copyFile(source, destination) {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
readStream.on('error', (err) => {
console.error('读取错误:', err);
});
writeStream.on('error', (err) => {
console.error('写入错误:', err);
});
writeStream.on('finish', () => {
console.log(`文件已从 ${source} 复制到 ${destination}`);
});
readStream.pipe(writeStream);
}
// 使用示例
copyFile('large-video.mp4', 'backup-video.mp4');
案例 2: 递归读取目录树
const fs = require('fs').promises;
const path = require('path');
async function readDirRecursive(dirPath, indent = '') {
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
console.log(`${indent}📁 ${entry.name}/`);
await readDirRecursive(fullPath, indent + ' ');
} else {
const stats = await fs.stat(fullPath);
console.log(`${indent}📄 ${entry.name} (${stats.size} bytes)`);
}
}
} catch (err) {
console.error('错误:', err);
}
}
// 使用示例
readDirRecursive('./my-project');
案例 3: 简单的日志系统
const fs = require('fs');
const path = require('path');
class Logger {
constructor(logDir = './logs') {
this.logDir = logDir;
this.ensureLogDir();
}
ensureLogDir() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
getLogFilePath() {
const date = new Date().toISOString().split('T')[0];
return path.join(this.logDir, `${date}.log`);
}
log(level, message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] [${level}] ${message}\n`;
fs.appendFile(this.getLogFilePath(), logEntry, (err) => {
if (err) {
console.error('写入日志失败:', err);
}
});
console.log(logEntry.trim());
}
info(message) {
this.log('INFO', message);
}
error(message) {
this.log('ERROR', message);
}
warn(message) {
this.log('WARN', message);
}
}
// 使用示例
const logger = new Logger('./logs');
logger.info('应用程序启动');
logger.warn('这是一个警告');
logger.error('发生了一个错误');
案例 4: 文件批量重命名
const fs = require('fs').promises;
const path = require('path');
async function batchRename(dirPath, pattern, replacement) {
try {
const files = await fs.readdir(dirPath);
const regex = new RegExp(pattern);
let count = 0;
for (const file of files) {
if (regex.test(file)) {
const oldPath = path.join(dirPath, file);
const newName = file.replace(regex, replacement);
const newPath = path.join(dirPath, newName);
await fs.rename(oldPath, newPath);
console.log(`重命名: ${file} -> ${newName}`);
count++;
}
}
console.log(`\n总共重命名了 ${count} 个文件`);
} catch (err) {
console.error('批量重命名失败:', err);
}
}
// 使用示例: 将所有 .txt 文件重命名为 .md
batchRename('./docs', '\\.txt$', '.md');
案例 5: JSON 配置文件管理
const fs = require('fs').promises;
const path = require('path');
class ConfigManager {
constructor(configPath) {
this.configPath = configPath;
this.config = {};
}
async load() {
try {
const data = await fs.readFile(this.configPath, 'utf8');
this.config = JSON.parse(data);
console.log('配置加载成功');
return this.config;
} catch (err) {
if (err.code === 'ENOENT') {
console.log('配置文件不存在,创建默认配置');
await this.save();
} else {
throw err;
}
}
}
async save() {
try {
const data = JSON.stringify(this.config, null, 2);
await fs.writeFile(this.configPath, data, 'utf8');
console.log('配置保存成功');
} catch (err) {
console.error('保存配置失败:', err);
}
}
get(key) {
return this.config[key];
}
set(key, value) {
this.config[key] = value;
}
async update(key, value) {
this.set(key, value);
await this.save();
}
}
// 使用示例
async function main() {
const config = new ConfigManager('./config.json');
await config.load();
config.set('appName', 'My App');
config.set('version', '1.0.0');
config.set('port', 3000);
await config.save();
console.log('应用名称:', config.get('appName'));
console.log('端口:', config.get('port'));
}
main();
5. 最佳实践
5.1 错误处理
const fs = require('fs').promises;
async function safeReadFile(filePath) {
try {
const data = await fs.readFile(filePath, 'utf8');
return { success: true, data };
} catch (err) {
if (err.code === 'ENOENT') {
return { success: false, error: '文件不存在' };
} else if (err.code === 'EACCES') {
return { success: false, error: '没有权限' };
} else {
return { success: false, error: err.message };
}
}
}
5.2 使用 Promise API
const fs = require('fs').promises;
// 链式调用
fs.readFile('input.txt', 'utf8')
.then(data => data.toUpperCase())
.then(upperData => fs.writeFile('output.txt', upperData))
.then(() => console.log('处理完成'))
.catch(err => console.error(err));
// async/await (推荐)
async function processFile() {
try {
const data = await fs.readFile('input.txt', 'utf8');
const processed = data.toUpperCase();
await fs.writeFile('output.txt', processed);
console.log('处理完成');
} catch (err) {
console.error(err);
}
}
5.3 使用流处理大文件
const fs = require('fs');
// 不好的做法:读取大文件到内存
// const data = fs.readFileSync('huge-file.txt'); // 可能内存溢出
// 好的做法:使用流
const readStream = fs.createReadStream('huge-file.txt');
const writeStream = fs.createWriteStream('processed.txt');
readStream.pipe(writeStream);
5.4 路径处理
const path = require('path');
const fs = require('fs');
// 使用 path.join 构建路径
const filePath = path.join(__dirname, 'data', 'config.json');
// 获取文件扩展名
const ext = path.extname('file.txt'); // '.txt'
// 获取文件名
const basename = path.basename('/path/to/file.txt'); // 'file.txt'
// 获取目录名
const dirname = path.dirname('/path/to/file.txt'); // '/path/to'
6. 常见错误代码
| 错误代码 | 说明 |
|---|---|
| ENOENT | 文件或目录不存在 |
| EACCES | 权限不足 |
| EEXIST | 文件已存在 |
| EISDIR | 期望文件但是是目录 |
| ENOTDIR | 期望目录但是是文件 |
| EMFILE | 打开的文件太多 |
7. 性能建议
- 大文件使用流: 避免一次性读取大文件到内存
- 批量操作: 使用
Promise.all()并行处理多个文件 - 缓存文件信息: 避免重复调用
stat() - 使用异步方法: 避免阻塞事件循环
- 合理使用缓冲区大小: 在流操作中设置适当的
highWaterMark
// 并行读取多个文件
const fs = require('fs').promises;
async function readMultipleFiles(filePaths) {
const promises = filePaths.map(path => fs.readFile(path, 'utf8'));
const results = await Promise.all(promises);
return results;
}
// 使用
readMultipleFiles(['file1.txt', 'file2.txt', 'file3.txt'])
.then(contents => console.log(contents));
8. 总结
fs模块是 Node.js 核心模块,无需安装- 优先使用异步方法,避免阻塞
- Promise API 配合 async/await 是最现代的方式
- 大文件使用流式处理
- 注意错误处理和路径规范化
- 了解常见错误代码,提供友好的错误信息
更多推荐
所有评论(0)