JavaScript Node.js文件系统
简介
Node.js的文件系统(fs模块)是Node.js核心API的重要组成部分,它提供了与文件系统进行交互的功能。无论是读取文件、写入文件、修改文件权限,还是创建目录、列出目录内容,fs模块都能帮助你完成这些操作。
在Web开发中,文件系统操作是服务器端编程的重要部分,例如:日志记录、配置文件管理、上传文件处理等。掌握Node.js文件系统操作是成为后端开发者的基础技能。
文件系统模块概览
要使用文件系统功能,首先需要导入fs模块:
const fs = require('fs');
Node.js的fs模块提供了两种API:
- 同步API:函数名通常以"Sync"结尾,如
readFileSync
- 异步API:使用回调函数或Promise,如
readFile
在生产环境中,应尽量使用异步API,避免阻塞主线程。同步API适合简单脚本或启动配置阶段使用。
基本文件操作
读取文件
同步读取文件
const fs = require('fs');
try {
// 同步读取文件
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('读取文件出错:', err);
}
// 输出: 文件中的内容
异步读取文件 (回调方式)
const fs = require('fs');
// 异步读取文件
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取文件出错:', err);
return;
}
console.log(data);
});
console.log('这行会在文件读取操作启动后立即执行');
// 输出:
// 这行会在文件读取操作启动后立即执行
// 文件中的内容
异步读取文件 (Promise方式)
const fs = require('fs').promises;
// 使用Promise读取文件
fs.readFile('example.txt', 'utf8')
.then(data => {
console.log(data);
})
.catch(err => {
console.error('读取文件出错:', err);
});
console.log('这行会在文件读取操作启动后立即执行');
异步读取文件 (async/await方式)
const fs = require('fs').promises;
async function readMyFile() {
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('读取文件出错:', err);
}
}
readMyFile();
写入文件
同步写入文件
const fs = require('fs');
try {
// 同步写入文件
fs.writeFileSync('output.txt', '这是要写入的内容', 'utf8');
console.log('文件写入成功');
} catch (err) {
console.error('写入文件出错:', err);
}
异步写入文件
const fs = require('fs');
// 异步写入文件
fs.writeFile('output.txt', '这是要写入的内容', 'utf8', (err) => {
if (err) {
console.error('写入文件出错:', err);
return;
}
console.log('文件写入成功');
});
追加文件内容
如果你想在文件末尾追加内容而不是覆盖原有内容:
const fs = require('fs');
// 异步追加内容到文件
fs.appendFile('log.txt', '新的日志内容\n', (err) => {
if (err) {
console.error('追加文件出错:', err);
return;
}
console.log('内容已追加到文件');
});
检查文件是否存在
const fs = require('fs');
// 检查文件是否存在
fs.access('example.txt', fs.constants.F_OK, (err) => {
console.log(`${err ? '文件不存在' : '文件存在'}`);
});
// 同步方法
try {
fs.accessSync('example.txt', fs.constants.F_OK);
console.log('文件存在');
} catch (err) {
console.log('文件不存在');
}
从Node.js v10.0.0起,fs.exists()
和fs.existsSync()
被标记为废弃,建议使用fs.access()
和fs.accessSync()
替代。
目录操作
创建目录
const fs = require('fs');
// 创建目录
fs.mkdir('newDirectory', (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('目录创建成功');
});
创建嵌套目录
const fs = require('fs');
// 创建嵌套目录
fs.mkdir('parent/child/grandchild', { recursive: true }, (err) => {
if (err) {
console.error('创建嵌套目录失败:', err);
return;
}
console.log('嵌套目录创建成功');
});
读取目录内容
const fs = require('fs');
// 读取目录内容
fs.readdir('.', (err, files) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
console.log('目录内容:', files);
});
// 输出:目录内容: [ 'file1.txt', 'file2.js', 'folder1', ... ]
删除目录
const fs = require('fs');
// 删除空目录
fs.rmdir('emptyDirectory', (err) => {
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录删除成功');
});
// 递归删除目录及其内容(Node.js v14.14.0+)
fs.rm('directory', { recursive: true }, (err) => {
if (err) {
console.error('递归删除目录失败:', err);
return;
}
console.log('目录及其内容已删除');
});
文件信息和状态
获取文件信息
const fs = require('fs');
// 获取文件信息
fs.stat('example.txt', (err, stats) => {
if (err) {
console.error('获取文件信息失败:', err);
return;
}
console.log('文件大小:', stats.size, '字节');
console.log('是否为文件:', stats.isFile());
console.log('是否为目录:', stats.isDirectory());
console.log('最后修改时间:', stats.mtime);
console.log('创建时间:', stats.birthtime);
});
文件路径操作
Node.js提供了path
模块来处理文件路径:
const path = require('path');
// 路径拼接
const fullPath = path.join(__dirname, 'files', 'example.txt');
console.log('拼接路径:', fullPath);
// 获取文件名
console.log('文件名:', path.basename(fullPath));
// 获取目录名
console.log('目录名:', path.dirname(fullPath));
// 获取扩展名
console.log('扩展名:', path.extname(fullPath));
// 解析路径
const pathObj = path.parse(fullPath);
console.log('路径对象:', pathObj);
// 输出: {
// root: '/',
// dir: '/your/directory/path/files',
// base: 'example.txt',
// ext: '.txt',
// name: 'example'
// }
// 规范化路径
console.log('规范化路径:', path.normalize('/path//to/..//file.txt'));
// 输出: /path/file.txt
文件流操作
对于大文件,使用流可以更高效地处理:
读取流
const fs = require('fs');
// 创建读取流
const readStream = fs.createReadStream('largeFile.txt', 'utf8');
readStream.on('data', (chunk) => {
console.log('接收到数据块:', chunk.length, '字节');
});
readStream.on('end', () => {
console.log('已读取所有数据');
});
readStream.on('error', (err) => {
console.error('读取出错:', err);
});
写入流
const fs = require('fs');
// 创建写入流
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('第一行内容\n');
writeStream.write('第二行内容\n');
writeStream.end('最后一行内容');
writeStream.on('finish', () => {
console.log('写入已完成');
});
writeStream.on('error', (err) => {
console.error('写入出错:', err);
});
管道操作 - 复制文件
const fs = require('fs');
// 创建读取和写入流
const readStream = fs.createReadStream('source.txt');
const writeStream = fs.createWriteStream('destination.txt');
// 将读取流数据通过管道传输到写入流
readStream.pipe(writeStream);
readStream.on('error', (err) => {
console.error('读取出错:', err);
});
writeStream.on('error', (err) => {
console.error('写入出错:', err);
});
writeStream.on('finish', () => {
console.log('文件复制完成');
});
实际应用案例
案例1:简单的日志记录系统
const fs = require('fs');
const path = require('path');
class SimpleLogger {
constructor(logDir) {
this.logDir = logDir;
this.ensureLogDirExists();
}
ensureLogDirExists() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
getLogFilePath() {
const now = new Date();
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
return path.join(this.logDir, `${dateStr}.log`);
}
log(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}\n`;
fs.appendFile(this.getLogFilePath(), logEntry, (err) => {
if (err) {
console.error('日志写入失败:', err);
}
});
}
info(message) {
this.log(`INFO: ${message}`);
}
error(message) {
this.log(`ERROR: ${message}`);
}
}
// 使用方式
const logger = new SimpleLogger('./logs');
logger.info('应用程序已启动');
logger.error('发生了一个错误');
案例2:文件监视器
const fs = require('fs');
const path = require('path');
const directoryToWatch = './watched';
// 确保目录存在
if (!fs.existsSync(directoryToWatch)) {
fs.mkdirSync(directoryToWatch);
}
console.log(`开始监视目录: ${directoryToWatch}`);
// 监视目录变化
fs.watch(directoryToWatch, (eventType, filename) => {
if (filename) {
const time = new Date().toLocaleTimeString();
console.log(`[${time}] ${eventType}: ${filename}`);
// 如果是新创建的文本文件,显示其内容
if (eventType === 'change' && path.extname(filename) === '.txt') {
try {
const content = fs.readFileSync(path.join(directoryToWatch, filename), 'utf8');
console.log(`文件内容: ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`);
} catch (err) {
// 文件可能已被删除
console.log('无法读取文件内容');
}
}
}
});
console.log('文件监视器已启动,在watched目录创建或修改文件来查看效果');
console.log('按Ctrl+C退出');
案例3:批量文件处理
假设我们需要处理一个目录下的所有JSON文件,提取特定信息并生成报告:
const fs = require('fs').promises;
const path = require('path');
async function processJsonFiles(directory) {
try {
// 读取目录内容
const files = await fs.readdir(directory);
// 筛选JSON文件
const jsonFiles = files.filter(file => path.extname(file).toLowerCase() === '.json');
console.log(`找到 ${jsonFiles.length} 个JSON文件`);
let allData = [];
// 处理每个文件
for (const file of jsonFiles) {
const filePath = path.join(directory, file);
console.log(`处理文件: ${filePath}`);
// 读取文件内容
const content = await fs.readFile(filePath, 'utf8');
try {
// 解析JSON
const data = JSON.parse(content);
// 提取需要的信息(示例)
if (data.name && data.age) {
allData.push({
fileName: file,
name: data.name,
age: data.age
});
}
} catch (parseErr) {
console.error(`解析文件 ${file} 失败:`, parseErr.message);
}
}
// 生成报告
const report = JSON.stringify(allData, null, 2);
await fs.writeFile(path.join(directory, 'report.json'), report);
console.log(`报告已生成,包含 ${allData.length} 条记录`);
} catch (err) {
console.error('处理文件时出错:', err);
}
}
// 使用
processJsonFiles('./data');
文件系统操作的最佳实践
-
优先使用异步API:避免阻塞事件循环,特别是在处理较大文件或高负载服务器。
-
正确处理错误:始终检查和处理文件操作中的潜在错误。
-
路径处理:使用
path
模块而不是手动字符串拼接,确保跨平台兼容性。 -
大文件使用流:处理大文件时,使用流API而不是一次性读取整个文件。
-
关闭文件句柄:使用完文件后,确保关闭文件句柄(使用高级API如
readFile
时,Node.js会自动处理)。 -
避免竞态条件:当多个操作同时访问同一文件时,小心处理可能的竞态条件。
-
权限和安全性:注意文件访问权限,特别是在Web应用中处理用户上传的文件。
总结
Node.js的文件系统模块提供了强大而灵活的API,允许我们执行各种文件和目录操作。通过本教程,你应该已经学会了:
- 基本的文件读写操作
- 目录创建、读取和删除
- 获取文件信息和状态
- 使用路径模块处理文件路径
- 使用流处理大文件
- 在实际应用中应用这些概念
掌握文件系统操作对于构建后端应用程序、CLI工具、自动化脚本等都是必不可少的技能。随着你对Node.js理解的深入,这些知识将成为你开发工具箱中的重要组成部分。
练习和挑战
-
文件拷贝工具:创建一个命令行工具,可以将一个目录中的所有文件复制到另一个目录。
-
文件类型统计器:开发一个程序,统计指定目录中不同类型文件的数量和总大小。
-
简单的文件服务器:结合HTTP模块,创建一个简单的文件服务器,可以浏览和下载指定目录中的文件。
-
文件内容搜索:编写一个函数,可以在目录及其子目录中搜索包含特定文本的所有文件。
-
CSV数据处理:读取CSV文件,处理其中的数据,然后输出一个新的格式化CSV文件。