一、什么是fs模块?¶
在Node.js中,文件系统(File System)是处理文件和目录的核心模块,我们通常通过fs模块来操作。想象一下,如果你用浏览器写前端代码,只能在浏览器中操作内存里的数据,但Node.js是运行在服务器端的,它可以直接和电脑的硬盘打交道,而fs模块就是Node.js提供的“硬盘操作工具包”。
比如,我们可以用它读取本地的JSON文件、创建文件夹、写入日志等。fs模块的API分为同步和异步两种,同步方法会阻塞代码执行直到完成,而异步方法则不会,更适合高并发场景。
二、同步vs异步:该选哪种?¶
在开始写代码前,先搞清楚同步和异步的区别:
- 同步方法:像排队买奶茶,你必须站在那等,直到拿到奶茶才能继续。特点是简单直接,但会阻塞后续代码。
- 异步方法:像提前点单,下单后可以去做别的事,奶茶做好了会通知你。特点是非阻塞,效率更高,适合Node.js的单线程模型。
初学者建议:优先学习异步方法,它更符合Node.js的设计理念。如果逻辑简单(比如读取小文件),也可以用同步方法快速实现。
三、基础文件读写操作¶
1. 引入fs模块¶
首先,在代码里引入fs模块,就像引入工具包一样:
const fs = require('fs'); // 引入核心模块
2. 异步读取文件(readFile)¶
用fs.readFile方法异步读取文件,语法如下:
fs.readFile('文件路径', '编码格式', (err, data) => {
// 回调函数会在读取完成后执行
if (err) {
console.error('读取失败:', err);
return; // 出错时终止后续操作
}
console.log('文件内容:', data);
});
参数解释:
- 文件路径:可以是相对路径(如'./test.txt')或绝对路径(如'/Users/yourname/test.txt')。
- 编码格式:如'utf8'(返回字符串),不写则返回Buffer(二进制数据)。
- 回调函数:接收两个参数:err(错误信息)和data(文件内容)。
示例:创建一个test.txt文件,内容为"Hello, Node.js!",执行以下代码:
const fs = require('fs');
// 异步读取test.txt(相对路径)
fs.readFile('./test.txt', 'utf8', (err, data) => {
if (err) {
console.error('读取失败:', err);
return;
}
console.log('文件内容:', data); // 输出:文件内容: Hello, Node.js!
});
3. 同步读取文件(readFileSync)¶
如果想让代码“顺序执行”,可以用同步方法fs.readFileSync,错误用try/catch捕获:
try {
const data = fs.readFileSync('./test.txt', 'utf8');
console.log('同步读取内容:', data);
} catch (err) {
console.error('同步读取失败:', err);
}
区别:同步方法会阻塞后续代码,直到文件读取完成。适合简单逻辑,比如初始化配置文件时。
4. 异步写入文件(writeFile)¶
用fs.writeFile写入文件,默认会覆盖已有内容(如果文件不存在则自动创建):
fs.writeFile('output.txt', '这是写入的内容', (err) => {
if (err) {
console.error('写入失败:', err);
return;
}
console.log('写入成功!');
});
示例:执行后会在当前目录生成output.txt,内容为"这是写入的内容"。
注意:如果想追加内容而不是覆盖,用fs.appendFile:
fs.appendFile('output.txt', '\n这是追加的内容', (err) => {
if (err) {
console.error('追加失败:', err);
return;
}
console.log('追加成功!');
});
5. 路径处理:避免“找不到文件”的坑¶
路径是文件操作的关键,Node.js中path模块可以帮我们更安全地处理路径(如跨平台拼接路径)。先简单引入path模块:
const path = require('path');
常用路径方法:
- path.join(__dirname, 'subdir', 'file.txt'):拼接路径,自动处理/或\(Windows系统自动转换)。
- __dirname:当前模块所在目录的绝对路径(动态变量,永远指向当前文件所在文件夹)。
- __filename:当前模块的绝对路径(包含文件名)。
示例:用path.join拼接路径:
const fs = require('fs');
const path = require('path');
// 拼接路径:__dirname + 'data' + 'test.txt'
const filePath = path.join(__dirname, 'data', 'test.txt');
// 读取该路径下的文件
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('路径错误:', err);
return;
}
console.log('正确读取到文件:', data);
});
四、目录操作:创建、读取、删除¶
1. 创建目录(mkdir)¶
用fs.mkdir创建目录,默认只创建最外层目录,嵌套目录需要递归创建:
fs.mkdir('./newDir', (err) => {
if (err) {
console.error('创建目录失败:', err);
return;
}
console.log('目录创建成功!');
});
递归创建多级目录:需要加recursive: true参数:
fs.mkdir('./a/b/c', { recursive: true }, (err) => { // 允许创建嵌套目录
if (err) {
console.error('创建多级目录失败:', err);
return;
}
console.log('多级目录创建成功!');
});
2. 读取目录内容(readdir)¶
用fs.readdir列出目录下的所有文件和子目录:
fs.readdir('./newDir', (err, files) => {
if (err) {
console.error('读取目录失败:', err);
return;
}
console.log('目录内容:', files); // 输出数组,如 ['file1.txt', 'subDir']
});
3. 删除目录(rmdir)¶
用fs.rmdir删除目录,仅支持删除空目录:
fs.rmdir('./emptyDir', (err) => { // 确保目录为空才能删除
if (err) {
console.error('删除目录失败:', err);
return;
}
console.log('目录删除成功!');
});
五、文件信息与权限检查¶
1. 检查文件是否存在(existsSync)¶
用fs.existsSync快速检查文件/目录是否存在(同步方法):
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'test.txt');
if (fs.existsSync(filePath)) {
console.log('文件存在!');
fs.readFile(filePath, 'utf8', (err, data) => { /* ... */ });
} else {
console.log('文件不存在!');
}
2. 获取文件/目录信息(stat)¶
用fs.stat获取文件详细信息(大小、创建时间、是否为目录等):
fs.stat('./test.txt', (err, stats) => {
if (err) {
console.error('获取信息失败:', err);
return;
}
console.log('是否为文件:', stats.isFile()); // true
console.log('文件大小:', stats.size, '字节'); // 15字节(示例)
});
六、常见问题与解决¶
1. 文件路径错误怎么办?¶
原因:相对路径的基准是“执行node命令的目录”,而非脚本所在目录。
解决:用__dirname(当前脚本所在目录)拼接路径,或直接用绝对路径。
// 错误示例(可能找不到文件)
fs.readFile('test.txt', ...); // 依赖执行node命令的位置
// 正确示例
fs.readFile(path.join(__dirname, 'test.txt'), ...); // 基于脚本位置的绝对路径
2. 如何处理大文件?¶
小文件用readFile/writeFile即可,但大文件(如视频、日志)会占用内存。此时推荐使用流(Stream),它会分块读取数据:
const fs = require('fs');
const readStream = fs.createReadStream('largeFile.txt');
const writeStream = fs.createWriteStream('copiedFile.txt');
// 管道流:数据从readStream流向writeStream
readStream.pipe(writeStream);
七、总结¶
fs模块是Node.js操作文件系统的基石,掌握它你就能实现:
- 读取配置文件、日志文件
- 创建/删除目录、文件
- 处理文件内容(读写、追加、复制)
实践建议:
1. 先从简单的“创建文件→写入内容→读取内容→删除文件”开始
2. 尝试用path模块处理不同平台的路径问题
3. 对比同步/异步方法的区别,理解事件循环和非阻塞I/O的优势
记住,编程学习没有捷径,多敲代码、多踩坑、多调试,才能真正掌握文件系统操作的精髓!