fs(File System)模块是 Node.js 的核心模块之一,它提供了与文件系统进行交互的 API,包括读写文件、操作目录、修改权限、创建流等。所有文件系统操作都有同步和异步两种形式。本章将详细介绍 fs 模块的常用方法和最佳实践。
使用 require('fs') 引入模块。建议使用常量引用:
const fs = require('fs');
// 或使用 promises API(推荐)
const fsPromises = require('fs').promises;
fs 模块中的所有方法都有同步和异步两种版本。异步方法接受回调函数作为最后一个参数,同步方法则直接返回结果(或抛出异常)。在 Node.js 中,应优先使用异步方法以防止阻塞事件循环。
// 异步读取文件
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});
// 同步读取文件
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
// 使用 promises API(更优雅)
async function readFileAsync() {
try {
const data = await fsPromises.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
console.error(err);
}
}
fs.promises API,结合 async/await,代码更简洁且易于错误处理。
const fs = require('fs').promises;
async function read() {
// 不指定编码时返回 Buffer
const buffer = await fs.readFile('file.txt');
console.log(buffer.toString('utf8'));
// 指定编码直接返回字符串
const text = await fs.readFile('file.txt', 'utf8');
console.log(text);
}
对于大文件,使用流可以避免一次性占用过多内存。
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').promises;
async function write() {
await fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
console.log('文件写入成功');
}
const fs = require('fs').promises;
async function append() {
await fs.appendFile('output.txt', '\n新的一行', 'utf8');
console.log('内容已追加');
}
const fs = require('fs');
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('第一行\n');
writeStream.write('第二行\n');
writeStream.end('最后一行');
writeStream.on('finish', () => {
console.log('写入完成');
});
使用 stat 方法获取文件或目录的详细信息。
const fs = require('fs').promises;
async function getStats() {
const stats = await fs.stat('file.txt');
console.log('是文件?', stats.isFile());
console.log('是目录?', stats.isDirectory());
console.log('大小:', stats.size, '字节');
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
}
const fs = require('fs').promises;
async function makeDir() {
// 创建单层目录
await fs.mkdir('newdir');
// 递归创建多层目录
await fs.mkdir('parent/child/grandchild', { recursive: true });
}
const fs = require('fs').promises;
async function readDir() {
const files = await fs.readdir('./');
console.log('当前目录下的文件:', files);
}
const fs = require('fs').promises;
async function removeDir() {
// 删除空目录
await fs.rmdir('emptydir');
// 递归删除非空目录(Node.js 16+ 支持 recursive 选项)
await fs.rm('nonemptydir', { recursive: true, force: true });
}
const fs = require('fs').promises;
async function rename() {
await fs.rename('oldname.txt', 'newname.txt');
console.log('重命名成功');
}
const fs = require('fs').promises;
async function unlinkFile() {
await fs.unlink('file-to-delete.txt');
console.log('文件已删除');
}
使用 fs.watch 或 fs.watchFile 监视文件或目录的变化。
const fs = require('fs');
// 监视目录
fs.watch('./', (eventType, filename) => {
console.log(`事件类型:${eventType},文件:${filename}`);
});
// 监视文件(轮询方式,不推荐频繁使用)
fs.watchFile('file.txt', (curr, prev) => {
console.log('文件修改时间:', curr.mtime);
});
fs.watch 在不同平台上的行为可能不一致,且并非所有平台都支持。对于关键应用,建议使用第三方库如 chokidar。
const fs = require('fs').promises;
async function chmodExample() {
// 修改权限为 rw-r--r-- (644)
await fs.chmod('file.txt', 0o644);
}
async function chownExample() {
// 修改所有者和组(需要 root 权限)
await fs.chown('file.txt', uid, gid);
}
对于更细粒度的控制,可以使用文件描述符(file descriptor)。
const fs = require('fs').promises;
async function fdExample() {
const fd = await fs.open('file.txt', 'r'); // 'r' 读取模式
const buffer = Buffer.alloc(100);
const { bytesRead } = await fd.read(buffer, 0, 100, 0);
console.log(`读取了 ${bytesRead} 字节:`, buffer.toString('utf8', 0, bytesRead));
await fd.close();
}
在打开文件时,可以使用以下标志:
'r' - 读取,文件不存在则抛出异常。'r+' - 读写,文件不存在则抛出异常。'w' - 写入,文件不存在则创建,存在则截断。'wx' - 排他写入,文件已存在则失败。'a' - 追加,文件不存在则创建。'ax' - 排他追加。fs 常与 path 模块配合使用,构建安全的文件路径。
const path = require('path');
const fs = require('fs').promises;
async function safePath() {
const filePath = path.join(__dirname, 'data', 'file.txt');
const data = await fs.readFile(filePath, 'utf8');
console.log(data);
}
异步方法通常通过回调或 Promise 的 catch 处理错误。常见错误码如 ENOENT(文件不存在)、EACCES(权限不足)。
const fs = require('fs').promises;
async function safeRead() {
try {
await fs.readFile('nonexistent.txt');
} catch (err) {
if (err.code === 'ENOENT') {
console.error('文件不存在');
} else {
console.error('其他错误:', err);
}
}
}
const fs = require('fs').promises;
const path = require('path');
async function walkDir(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await walkDir(fullPath);
} else {
console.log(fullPath);
}
}
}
walkDir('./').catch(console.error);
fs 模块是 Node.js 文件操作的基石。掌握其同步/异步 API、流式操作和目录管理对于开发实用工具和后端应用至关重要。在可能的情况下,优先使用 fs.promises 和流以提高性能和代码可读性。下一章我们将介绍另一个核心模块 path,用于处理文件和目录路径。