文件系统操作
🎯 学习目标
- 掌握Node.js文件系统模块的基本使用
- 学会同步和异步文件操作
- 理解文件流的概念和使用
- 掌握目录操作和路径处理
- 学会文件监控和事件处理
- 了解文件权限和安全考虑
📚 文件系统模块基础
引入fs模块
javascript
const fs = require('fs');
const path = require('path');
// 或者使用Promise版本
const fsPromises = require('fs').promises;
同步 vs 异步操作
javascript
// 同步操作(阻塞)
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
} catch (error) {
console.error('读取文件失败:', error);
}
// 异步操作(非阻塞)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件失败:', err);
return;
}
console.log(data);
});
📁 文件读取和写入
基本文件操作
javascript
const fs = require('fs');
// 读取文件
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
// 写入文件
const content = 'Hello, Node.js!';
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('文件写入成功');
});
// 追加内容
fs.appendFile('log.txt', '新的日志条目\n', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('内容追加成功');
});
使用Promise版本
javascript
const fsPromises = require('fs').promises;
async function fileOperations() {
try {
// 读取文件
const data = await fsPromises.readFile('data.txt', 'utf8');
console.log('文件内容:', data);
// 写入文件
await fsPromises.writeFile('output.txt', 'Hello, World!', 'utf8');
console.log('文件写入成功');
// 追加内容
await fsPromises.appendFile('log.txt', '新的日志条目\n');
console.log('内容追加成功');
} catch (error) {
console.error('操作失败:', error);
}
}
fileOperations();
文件存在性检查
javascript
// 检查文件是否存在
fs.access('file.txt', fs.constants.F_OK, (err) => {
if (err) {
console.log('文件不存在');
} else {
console.log('文件存在');
}
});
// 检查文件权限
fs.access('file.txt', fs.constants.R_OK | fs.constants.W_OK, (err) => {
if (err) {
console.log('文件不可读写');
} else {
console.log('文件可读写');
}
});
// 使用stat获取文件信息
fs.stat('file.txt', (err, stats) => {
if (err) {
console.error('获取文件信息失败:', err);
return;
}
console.log('文件信息:');
console.log('大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
console.log('是否为文件:', stats.isFile());
console.log('是否为目录:', stats.isDirectory());
});
📂 目录操作
创建和删除目录
javascript
// 创建目录
fs.mkdir('new-directory', (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('目录创建成功');
});
// 创建嵌套目录
fs.mkdir('path/to/nested/directory', { recursive: true }, (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('嵌套目录创建成功');
});
// 删除目录
fs.rmdir('empty-directory', (err) => {
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录删除成功');
});
// 删除非空目录(递归)
fs.rm('directory-with-files', { recursive: true, force: true }, (err) => {
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录及其内容删除成功');
});
读取目录内容
javascript
// 读取目录内容
fs.readdir('.', (err, files) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
console.log('目录内容:');
files.forEach(file => {
console.log('-', file);
});
});
// 读取目录详细信息
fs.readdir('.', { withFileTypes: true }, (err, files) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
files.forEach(file => {
const type = file.isDirectory() ? '目录' : '文件';
console.log(`${type}: ${file.name}`);
});
});
🌊 文件流
读取流
javascript
const fs = require('fs');
// 创建读取流
const readStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 1024 // 缓冲区大小
});
// 监听数据事件
readStream.on('data', (chunk) => {
console.log('接收到数据块:', chunk.length, '字节');
console.log('内容:', chunk.substring(0, 100) + '...');
});
// 监听结束事件
readStream.on('end', () => {
console.log('文件读取完成');
});
// 监听错误事件
readStream.on('error', (err) => {
console.error('读取流错误:', err);
});
写入流
javascript
// 创建写入流
const writeStream = fs.createWriteStream('output.txt', {
encoding: 'utf8',
flags: 'w' // 写入模式
});
// 写入数据
writeStream.write('第一行数据\n');
writeStream.write('第二行数据\n');
writeStream.write('第三行数据\n');
// 结束写入
writeStream.end();
// 监听完成事件
writeStream.on('finish', () => {
console.log('文件写入完成');
});
// 监听错误事件
writeStream.on('error', (err) => {
console.error('写入流错误:', err);
});
管道操作
javascript
// 使用管道复制文件
const readStream = fs.createReadStream('source.txt');
const writeStream = fs.createWriteStream('destination.txt');
readStream.pipe(writeStream);
writeStream.on('finish', () => {
console.log('文件复制完成');
});
// 带错误处理的管道
readStream
.on('error', (err) => {
console.error('读取错误:', err);
writeStream.destroy();
})
.pipe(writeStream)
.on('error', (err) => {
console.error('写入错误:', err);
});
📍 路径处理
path模块使用
javascript
const path = require('path');
const filePath = '/Users/username/Documents/file.txt';
// 获取路径的不同部分
console.log('目录名:', path.dirname(filePath));
console.log('文件名:', path.basename(filePath));
console.log('扩展名:', path.extname(filePath));
console.log('文件名(无扩展名):', path.basename(filePath, path.extname(filePath)));
// 路径拼接
const dir = '/Users/username/Documents';
const filename = 'file.txt';
const fullPath = path.join(dir, filename);
console.log('完整路径:', fullPath);
// 路径解析
const parsed = path.parse(filePath);
console.log('解析结果:', parsed);
// 输出: { root: '/', dir: '/Users/username/Documents', base: 'file.txt', ext: '.txt', name: 'file' }
// 路径规范化
const normalized = path.normalize('/Users/username/../Documents/./file.txt');
console.log('规范化路径:', normalized);
// 相对路径
const relative = path.relative('/Users/username', '/Users/username/Documents/file.txt');
console.log('相对路径:', relative);
// 绝对路径
const absolute = path.resolve('file.txt');
console.log('绝对路径:', absolute);
👀 文件监控
监控文件变化
javascript
// 监控文件
fs.watchFile('file.txt', (curr, prev) => {
console.log('文件发生变化');
console.log('当前修改时间:', curr.mtime);
console.log('之前修改时间:', prev.mtime);
});
// 停止监控
setTimeout(() => {
fs.unwatchFile('file.txt');
console.log('停止监控文件');
}, 10000);
// 使用watch方法(更高效)
const watcher = fs.watch('directory', (eventType, filename) => {
console.log(`事件类型: ${eventType}`);
console.log(`文件名: ${filename}`);
});
// 关闭监控器
setTimeout(() => {
watcher.close();
console.log('监控器已关闭');
}, 10000);
🛠️ 实践项目:文件管理器
让我们创建一个简单的文件管理器项目:
1. 项目结构
file-manager/
├── package.json
├── index.js
├── lib/
│ ├── fileManager.js
│ ├── pathUtils.js
│ └── logger.js
├── data/
└── tests/
└── fileManager.test.js
2. 创建文件管理器类
javascript
// lib/fileManager.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
const mkdir = promisify(fs.mkdir);
const rmdir = promisify(fs.rmdir);
const unlink = promisify(fs.unlink);
class FileManager {
constructor(basePath = '.') {
this.basePath = path.resolve(basePath);
}
// 获取文件信息
async getFileInfo(filePath) {
try {
const fullPath = path.join(this.basePath, filePath);
const stats = await stat(fullPath);
return {
name: path.basename(fullPath),
path: filePath,
size: stats.size,
isFile: stats.isFile(),
isDirectory: stats.isDirectory(),
createdAt: stats.birthtime,
modifiedAt: stats.mtime,
permissions: stats.mode
};
} catch (error) {
throw new Error(`获取文件信息失败: ${error.message}`);
}
}
// 列出目录内容
async listDirectory(dirPath = '.') {
try {
const fullPath = path.join(this.basePath, dirPath);
const files = await readdir(fullPath, { withFileTypes: true });
const fileList = await Promise.all(
files.map(async (file) => {
const filePath = path.join(dirPath, file.name);
return await this.getFileInfo(filePath);
})
);
return fileList;
} catch (error) {
throw new Error(`列出目录失败: ${error.message}`);
}
}
// 创建文件
async createFile(filePath, content = '') {
try {
const fullPath = path.join(this.basePath, filePath);
const dir = path.dirname(fullPath);
// 确保目录存在
await this.ensureDirectoryExists(dir);
await writeFile(fullPath, content, 'utf8');
return await this.getFileInfo(filePath);
} catch (error) {
throw new Error(`创建文件失败: ${error.message}`);
}
}
// 读取文件
async readFile(filePath) {
try {
const fullPath = path.join(this.basePath, filePath);
const content = await readFile(fullPath, 'utf8');
return content;
} catch (error) {
throw new Error(`读取文件失败: ${error.message}`);
}
}
// 更新文件
async updateFile(filePath, content) {
try {
const fullPath = path.join(this.basePath, filePath);
await writeFile(fullPath, content, 'utf8');
return await this.getFileInfo(filePath);
} catch (error) {
throw new Error(`更新文件失败: ${error.message}`);
}
}
// 删除文件
async deleteFile(filePath) {
try {
const fullPath = path.join(this.basePath, filePath);
const stats = await stat(fullPath);
if (stats.isDirectory()) {
await rmdir(fullPath);
} else {
await unlink(fullPath);
}
return true;
} catch (error) {
throw new Error(`删除文件失败: ${error.message}`);
}
}
// 创建目录
async createDirectory(dirPath) {
try {
const fullPath = path.join(this.basePath, dirPath);
await mkdir(fullPath, { recursive: true });
return await this.getFileInfo(dirPath);
} catch (error) {
throw new Error(`创建目录失败: ${error.message}`);
}
}
// 复制文件
async copyFile(sourcePath, destPath) {
try {
const sourceFullPath = path.join(this.basePath, sourcePath);
const destFullPath = path.join(this.basePath, destPath);
const destDir = path.dirname(destFullPath);
// 确保目标目录存在
await this.ensureDirectoryExists(destDir);
const content = await readFile(sourceFullPath, 'utf8');
await writeFile(destFullPath, content, 'utf8');
return await this.getFileInfo(destPath);
} catch (error) {
throw new Error(`复制文件失败: ${error.message}`);
}
}
// 移动文件
async moveFile(sourcePath, destPath) {
try {
await this.copyFile(sourcePath, destPath);
await this.deleteFile(sourcePath);
return await this.getFileInfo(destPath);
} catch (error) {
throw new Error(`移动文件失败: ${error.message}`);
}
}
// 搜索文件
async searchFiles(pattern, searchPath = '.') {
try {
const files = await this.listDirectory(searchPath);
const results = [];
for (const file of files) {
if (file.name.includes(pattern)) {
results.push(file);
}
// 如果是目录,递归搜索
if (file.isDirectory) {
const subResults = await this.searchFiles(pattern, path.join(searchPath, file.name));
results.push(...subResults);
}
}
return results;
} catch (error) {
throw new Error(`搜索文件失败: ${error.message}`);
}
}
// 确保目录存在
async ensureDirectoryExists(dirPath) {
try {
await stat(dirPath);
} catch (error) {
if (error.code === 'ENOENT') {
await mkdir(dirPath, { recursive: true });
} else {
throw error;
}
}
}
}
module.exports = FileManager;
3. 创建路径工具
javascript
// lib/pathUtils.js
const path = require('path');
class PathUtils {
static normalizePath(inputPath) {
return path.normalize(inputPath);
}
static joinPaths(...paths) {
return path.join(...paths);
}
static getFileName(filePath) {
return path.basename(filePath);
}
static getFileExtension(filePath) {
return path.extname(filePath);
}
static getDirectoryName(filePath) {
return path.dirname(filePath);
}
static getFileNameWithoutExtension(filePath) {
const basename = path.basename(filePath);
const ext = path.extname(filePath);
return basename.replace(ext, '');
}
static isAbsolutePath(inputPath) {
return path.isAbsolute(inputPath);
}
static getRelativePath(from, to) {
return path.relative(from, to);
}
static resolvePath(inputPath) {
return path.resolve(inputPath);
}
static parsePath(inputPath) {
return path.parse(inputPath);
}
static formatPath(parsedPath) {
return path.format(parsedPath);
}
static isValidFileName(fileName) {
// 检查文件名是否包含非法字符
const invalidChars = /[<>:"/\\|?*]/;
return !invalidChars.test(fileName) && fileName.length > 0;
}
static sanitizeFileName(fileName) {
// 清理文件名,移除非法字符
return fileName.replace(/[<>:"/\\|?*]/g, '_');
}
}
module.exports = PathUtils;
4. 创建日志记录器
javascript
// lib/logger.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const appendFile = promisify(fs.appendFile);
const mkdir = promisify(fs.mkdir);
class Logger {
constructor(logDir = 'logs') {
this.logDir = logDir;
this.ensureLogDir();
}
async ensureLogDir() {
try {
await mkdir(this.logDir, { recursive: true });
} catch (error) {
// 目录可能已存在,忽略错误
}
}
getLogFileName() {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return path.join(this.logDir, `${year}-${month}-${day}.log`);
}
formatLogEntry(level, message, data = {}) {
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
data
};
return JSON.stringify(logEntry) + '\n';
}
async writeLog(level, message, data = {}) {
try {
const logFile = this.getLogFileName();
const logEntry = this.formatLogEntry(level, message, data);
await appendFile(logFile, logEntry);
} catch (error) {
console.error('写入日志失败:', error);
}
}
info(message, data) {
console.log(`[INFO] ${message}`);
this.writeLog('INFO', message, data);
}
error(message, data) {
console.error(`[ERROR] ${message}`);
this.writeLog('ERROR', message, data);
}
warn(message, data) {
console.warn(`[WARN] ${message}`);
this.writeLog('WARN', message, data);
}
debug(message, data) {
console.log(`[DEBUG] ${message}`);
this.writeLog('DEBUG', message, data);
}
}
module.exports = Logger;
5. 创建主程序
javascript
// index.js
const FileManager = require('./lib/fileManager');
const Logger = require('./lib/logger');
const PathUtils = require('./lib/pathUtils');
class FileManagerApp {
constructor() {
this.fileManager = new FileManager('./data');
this.logger = new Logger('./logs');
}
async run() {
try {
this.logger.info('文件管理器启动');
// 创建测试目录
await this.fileManager.createDirectory('test');
this.logger.info('创建测试目录');
// 创建测试文件
await this.fileManager.createFile('test/hello.txt', 'Hello, World!');
await this.fileManager.createFile('test/readme.md', '# 测试文件\n这是一个测试文件。');
this.logger.info('创建测试文件');
// 列出目录内容
const files = await this.fileManager.listDirectory('test');
this.logger.info('列出目录内容', { fileCount: files.length });
files.forEach(file => {
console.log(`- ${file.name} (${file.isFile ? '文件' : '目录'})`);
});
// 读取文件内容
const content = await this.fileManager.readFile('test/hello.txt');
console.log('文件内容:', content);
// 复制文件
await this.fileManager.copyFile('test/hello.txt', 'test/hello-copy.txt');
this.logger.info('复制文件');
// 搜索文件
const searchResults = await this.fileManager.searchFiles('hello');
console.log('搜索结果:', searchResults.map(f => f.name));
// 获取文件信息
const fileInfo = await this.fileManager.getFileInfo('test/hello.txt');
console.log('文件信息:', fileInfo);
// 演示路径工具
console.log('\n=== 路径工具演示 ===');
const testPath = '/Users/username/Documents/file.txt';
console.log('文件名:', PathUtils.getFileName(testPath));
console.log('扩展名:', PathUtils.getFileExtension(testPath));
console.log('目录名:', PathUtils.getDirectoryName(testPath));
console.log('解析路径:', PathUtils.parsePath(testPath));
this.logger.info('文件管理器运行完成');
} catch (error) {
this.logger.error('文件管理器运行失败', { error: error.message });
console.error('错误:', error.message);
}
}
}
// 运行应用
if (require.main === module) {
const app = new FileManagerApp();
app.run();
}
module.exports = FileManagerApp;
6. 创建测试文件
javascript
// tests/fileManager.test.js
const fs = require('fs');
const path = require('path');
const FileManager = require('../lib/fileManager');
async function runTests() {
console.log('开始运行文件管理器测试...\n');
const testDir = path.join(__dirname, 'test-data');
const fileManager = new FileManager(testDir);
try {
// 清理测试目录
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
// 测试创建目录
console.log('测试创建目录...');
await fileManager.createDirectory('test-dir');
console.log('✅ 目录创建成功');
// 测试创建文件
console.log('测试创建文件...');
await fileManager.createFile('test-dir/test.txt', '测试内容');
console.log('✅ 文件创建成功');
// 测试读取文件
console.log('测试读取文件...');
const content = await fileManager.readFile('test-dir/test.txt');
if (content === '测试内容') {
console.log('✅ 文件读取成功');
} else {
console.log('❌ 文件读取失败');
}
// 测试获取文件信息
console.log('测试获取文件信息...');
const fileInfo = await fileManager.getFileInfo('test-dir/test.txt');
if (fileInfo.name === 'test.txt' && fileInfo.isFile) {
console.log('✅ 文件信息获取成功');
} else {
console.log('❌ 文件信息获取失败');
}
// 测试列出目录
console.log('测试列出目录...');
const files = await fileManager.listDirectory('test-dir');
if (files.length === 1 && files[0].name === 'test.txt') {
console.log('✅ 目录列表获取成功');
} else {
console.log('❌ 目录列表获取失败');
}
// 测试复制文件
console.log('测试复制文件...');
await fileManager.copyFile('test-dir/test.txt', 'test-dir/test-copy.txt');
const copiedContent = await fileManager.readFile('test-dir/test-copy.txt');
if (copiedContent === '测试内容') {
console.log('✅ 文件复制成功');
} else {
console.log('❌ 文件复制失败');
}
// 测试搜索文件
console.log('测试搜索文件...');
const searchResults = await fileManager.searchFiles('test');
if (searchResults.length >= 2) {
console.log('✅ 文件搜索成功');
} else {
console.log('❌ 文件搜索失败');
}
console.log('\n✅ 所有测试通过!');
} catch (error) {
console.error('❌ 测试失败:', error.message);
} finally {
// 清理测试目录
try {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
console.log('测试文件已清理');
}
} catch (error) {
console.log('清理测试文件失败:', error.message);
}
}
}
// 运行测试
if (require.main === module) {
runTests();
}
module.exports = { runTests };
7. 更新package.json
json
{
"name": "file-manager",
"version": "1.0.0",
"description": "Node.js文件管理器项目",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "node tests/fileManager.test.js"
},
"keywords": ["file", "manager", "filesystem", "node"],
"author": "您的名字",
"license": "MIT"
}
📝 总结
在这一章中,我们学习了:
- 文件系统基础:fs模块的基本使用
- 文件操作:读取、写入、删除文件
- 目录操作:创建、删除、列出目录
- 文件流:处理大文件的流式操作
- 路径处理:path模块的使用
- 文件监控:监控文件变化
- 实践项目:创建了一个完整的文件管理器
🔗 下一步
接下来我们将学习:
继续学习,掌握Node.js的网络编程!🚀