文件系统
Node.js 的 fs 模块提供了文件系统操作能力,包括文件读写、目录管理、文件监听等功能。
fs 模块概述
fs 模块同时提供同步和异步两种 API:
导入方式
// CommonJS
const fs = require('node:fs');
const { readFile, writeFile } = require('node:fs');
const fsPromises = require('node:fs/promises');
// ES Modules
import fs from 'node:fs';
import { readFile, writeFile } from 'node:fs';
import fsPromises from 'node:fs/promises';
文件读取
异步读取
const fs = require('node:fs');
// 回调方式
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data);
});
// Promise 方式
const fsPromises = require('node:fs/promises');
async function readFile() {
try {
const data = await fsPromises.readFile('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
}
readFile();
同步读取
const fs = require('node:fs');
try {
const data = fs.readFileSync('example.txt', 'utf8');
console.log('文件内容:', data);
} catch (err) {
console.error('读取失败:', err);
}
读取选项
// 读取为 Buffer(默认)
const buffer = fs.readFileSync('image.png');
console.log(buffer); // <Buffer 89 50 4e 47 ...>
// 指定编码读取
const text = fs.readFileSync('example.txt', 'utf8');
// 使用选项对象
const data = fs.readFileSync('example.txt', {
encoding: 'utf8',
flag: 'r' // 读取模式
});
文件写入
异步写入
const fsPromises = require('node:fs/promises');
async function writeFile() {
try {
await fsPromises.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
console.log('写入成功');
} catch (err) {
console.error('写入失败:', err);
}
}
writeFile();
写入模式
const fs = require('node:fs');
// 覆盖写入(默认)
fs.writeFileSync('output.txt', '第一行');
// 追加写入
fs.writeFileSync('output.txt', '\n第二行', { flag: 'a' });
// 或使用 appendFile
fs.appendFileSync('output.txt', '\n第三行');
写入选项
fs.writeFileSync('output.txt', 'content', {
encoding: 'utf8',
mode: 0o644, // 文件权限
flag: 'w', // 写入模式
});
// 常用 flag:
// 'r' - 读取(文件必须存在)
// 'r+' - 读写(文件必须存在)
// 'w' - 写入(覆盖或创建)
// 'w+' - 读写(覆盖或创建)
// 'a' - 追加(文件不存在则创建)
// 'a+' - 读取追加(文件不存在则创建)
// 'x' - 排他创建(文件已存在则失败)
文件操作
复制文件
const fs = require('node:fs');
// 同步复制
fs.copyFileSync('source.txt', 'destination.txt');
// 异步复制
fs.copyFile('source.txt', 'destination.txt', (err) => {
if (err) throw err;
console.log('复制成功');
});
// Promise 方式
await fs.promises.copyFile('source.txt', 'destination.txt');
移动/重命名文件
const fs = require('node:fs');
// 重命名
fs.renameSync('old-name.txt', 'new-name.txt');
// 移动文件
fs.renameSync('file.txt', 'new-dir/file.txt');
// 异步方式
await fs.promises.rename('old.txt', 'new.txt');
删除文件
const fs = require('node:fs');
// 删除文件
fs.unlinkSync('file-to-delete.txt');
// 异步删除
await fs.promises.unlink('file-to-delete.txt');
// Node.js 14.14+ 使用 rm
await fs.promises.rm('file.txt');
await fs.promises.rm('directory', { recursive: true, force: true });
检查文件是否存在
const fs = require('node:fs');
// 已弃用的方式(不推荐)
fs.existsSync('file.txt');
// 推荐方式:检查可访问性
fs.accessSync('file.txt', fs.constants.R_OK);
// Promise 方式
try {
await fs.promises.access('file.txt', fs.constants.R_OK);
console.log('文件存在且可读');
} catch {
console.log('文件不存在或不可读');
}
// 检查文件状态
const stats = await fs.promises.stat('file.txt');
console.log('是文件:', stats.isFile());
console.log('是目录:', stats.isDirectory());
目录操作
创建目录
const fs = require('node:fs');
// 创建单层目录
fs.mkdirSync('new-dir');
// 递归创建目录
fs.mkdirSync('deep/nested/dir', { recursive: true });
// 异步方式
await fs.promises.mkdir('new-dir', { recursive: true });
读取目录
const fs = require('node:fs');
// 读取目录内容
const files = fs.readdirSync('my-directory');
console.log(files); // ['file1.txt', 'file2.txt', 'subdir']
// 包含文件类型
const entries = fs.readdirSync('my-directory', { withFileTypes: true });
entries.forEach(entry => {
console.log(`${entry.name} - ${entry.isFile() ? '文件' : '目录'}`);
});
// 异步方式
const files = await fs.promises.readdir('my-directory');
删除目录
const fs = require('node:fs');
// 删除空目录
fs.rmdirSync('empty-dir');
// 递归删除(Node.js 14.14+)
fs.rmSync('directory', { recursive: true, force: true });
// 异步方式
await fs.promises.rm('directory', { recursive: true, force: true });
文件信息
获取文件状态
const fs = require('node:fs');
const stats = fs.statSync('example.txt');
console.log('文件大小:', stats.size, '字节');
console.log('是文件:', stats.isFile());
console.log('是目录:', stats.isDirectory());
console.log('创建时间:', stats.birthtime);
console.log('修改时间:', stats.mtime);
console.log('访问时间:', stats.atime);
console.log('权限:', stats.mode.toString(8));
// 异步方式
const stats = await fs.promises.stat('example.txt');
Stats 对象属性
| 属性 | 说明 |
|---|---|
size | 文件大小(字节) |
isFile() | 是否为文件 |
isDirectory() | 是否为目录 |
isSymbolicLink() | 是否为符号链接 |
birthtime | 创建时间 |
mtime | 内容修改时间 |
atime | 访问时间 |
ctime | 状态修改时间 |
mode | 文件权限 |
uid | 所有者用户 ID |
gid | 所有者组 ID |
流式操作
对于大文件,使用流可以避免内存问题。
读取流
const fs = require('node: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);
});
写入流
const fs = require('node:fs');
// 创建写入流
const writeStream = fs.createWriteStream('output.txt');
// 写入数据
writeStream.write('第一行\n');
writeStream.write('第二行\n');
// 结束写入
writeStream.end('最后一行');
// 监听完成事件
writeStream.on('finish', () => {
console.log('写入完成');
});
管道操作
const fs = require('node:fs');
const zlib = require('node:zlib');
// 复制大文件
fs.createReadStream('source.txt')
.pipe(fs.createWriteStream('destination.txt'))
.on('finish', () => {
console.log('复制完成');
});
// 压缩文件
fs.createReadStream('source.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('source.txt.gz'))
.on('finish', () => {
console.log('压缩完成');
});
文件监听
watchFile
监听文件变化:
const fs = require('node:fs');
// 监听文件变化
fs.watchFile('example.txt', (curr, prev) => {
console.log('文件发生变化');
console.log('当前修改时间:', curr.mtime);
console.log('之前修改时间:', prev.mtime);
});
// 设置监听间隔
fs.watchFile('example.txt', { interval: 1000 }, (curr, prev) => {
console.log('文件被修改');
});
// 取消监听
fs.unwatchFile('example.txt');
watch
更高效的监听方式:
const fs = require('node:fs');
// 监听目录或文件
const watcher = fs.watch('./', (eventType, filename) => {
console.log(`事件类型: ${eventType}`);
console.log(`文件名: ${filename}`);
});
// 关闭监听
watcher.close();
// 使用异步迭代器(Node.js 18+)
async function watchDirectory() {
for await (const event of fs.promises.watch('./')) {
console.log(event);
}
}
路径处理
使用 path 模块处理路径:
const path = require('node:path');
// 路径拼接
const fullPath = path.join('/home', 'user', 'documents', 'file.txt');
console.log(fullPath); // '/home/user/documents/file.txt'
// 获取文件名
console.log(path.basename('/home/user/file.txt')); // 'file.txt'
console.log(path.basename('/home/user/file.txt', '.txt')); // 'file'
// 获取目录名
console.log(path.dirname('/home/user/file.txt')); // '/home/user'
// 获取扩展名
console.log(path.extname('file.txt')); // '.txt'
console.log(path.extname('file.min.js')); // '.js'
// 解析路径
console.log(path.parse('/home/user/file.txt'));
// {
// root: '/',
// dir: '/home/user',
// base: 'file.txt',
// ext: '.txt',
// name: 'file'
// }
// 格式化路径
console.log(path.format({
dir: '/home/user',
base: 'file.txt'
})); // '/home/user/file.txt'
// 绝对路径
console.log(path.resolve('file.txt'));
console.log(path.resolve('/home', 'user', 'file.txt')); // '/home/user/file.txt'
// 相对路径
console.log(path.relative('/home/user', '/home/guest')); // '../guest'
// 规范化路径
console.log(path.normalize('/home/../user/./documents')); // '/user/documents'
实用示例
递归遍历目录
const fs = require('node:fs/promises');
const path = require('node:path');
async function walkDirectory(dir) {
const files = [];
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
const subFiles = await walkDirectory(fullPath);
files.push(...subFiles);
} else {
files.push(fullPath);
}
}
return files;
}
// 使用
walkDirectory('./src').then(files => {
console.log('所有文件:', files);
});
复制目录
const fs = require('node:fs/promises');
const path = require('node:path');
async function copyDirectory(src, dest) {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await copyDirectory(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
}
}
// 使用
copyDirectory('./src', './backup');
按行读取文件
const fs = require('node:fs');
const readline = require('node:readline');
async function readLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
console.log(line);
}
}
readLines('data.txt');
小结
本章我们学习了:
- 文件读写:同步和异步方式、编码处理
- 文件操作:复制、移动、删除、检查存在
- 目录操作:创建、读取、删除
- 文件信息:获取文件状态和属性
- 流式操作:处理大文件
- 文件监听:监听文件和目录变化
- 路径处理:path 模块的使用
练习
- 实现一个递归复制目录的函数
- 使用流处理大文件,统计文件行数
- 创建一个文件监听器,当文件变化时自动重启服务
- 实现一个简单的文件搜索功能,在目录中搜索包含特定文本的文件