Skip to content

Node.js 文件系统 (File System)

Node.js 提供了一个内置模块,称为 fs(文件系统),用于与操作系统上的文件系统进行交互。它允许您读取、写入、更新、删除和管理文件和目录。

可以使用 require 导入 fs 模块:

const fs = require('fs');

或者,对于基于 Promise 的操作(推荐用于现代异步代码):

const fsPromises = require('fs').promises;
// 或者使用解构赋值:
const { readFile, writeFile } = require('fs').promises;

同步(Synchronous) vs. 异步(Asynchronous)操作

Section titled “同步(Synchronous) vs. 异步(Asynchronous)操作”

fs 模块中的大多数方法都有同步和异步两种形式。

  • 异步方法(例如,fs.readFile()、fs.writeFile()):这些方法在后台执行操作,不会阻塞 Node.js 事件循环(event loop)。它们通常接受一个回调函数(callback function)作为最后一个参数,该函数在操作完成(或出错)时执行。回调函数的第一个参数通常是一个错误对象(如果成功则为 null)。这是 Node.js 服务器中进行 I/O 操作的首选方法,以保持并发性和响应能力。
  • 同步方法(例如,fs.readFileSync()、fs.writeFileSync()):这些方法会阻塞整个 Node.js 进程,直到文件操作完成。虽然它们对于脚本或命令行工具来说更简单,但在处理并发请求的 Web 服务器或应用程序中应避免使用同步方法,因为它们会暂停执行并严重影响性能。
  • 基于 Promise 的方法(fs.promises):这些方法使用 JavaScript Promise 提供了一种替代的异步接口,通常与 async/await 语法一起使用,相比嵌套回调可以写出更清晰的异步代码。示例:await fsPromises.readFile(...)。

示例:读取文件(异步 vs. 同步)

Section titled “示例:读取文件(异步 vs. 同步)”

首先,创建一个名为 input.txt 的文本文件,内容如下:

Node.js Fundamentals
Learning about the file system module.

现在,创建一个名为 main.js 的 JavaScript 文件:

const fs = require('fs');
// 使用回调进行异步读取
console.log('Starting asynchronous read...');
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file asynchronously:', err);
return;
}
console.log('Asynchronous Read Content:\n' + data);
});
console.log('... Asynchronous read initiated, continuing execution ...');
// 同步读取
console.log('\nStarting synchronous read...');
try {
const dataSync = fs.readFileSync('input.txt', 'utf8');
console.log('Synchronous Read Content:\n' + dataSync);
} catch (err) {
console.error('Error reading file synchronously:', err);
}
console.log('... Synchronous read finished ...');
console.log('\nProgram Ended');

运行脚本:

$ node main.js

预期输出(异步部分的顺序可能略有不同):

Starting asynchronous read...
... Asynchronous read initiated, continuing execution ...
Starting synchronous read...
Synchronous Read Content:
Node.js Fundamentals
Learning about the file system module.
... Synchronous read finished ...
Program Ended
Asynchronous Read Content:
Node.js Fundamentals
Learning about the file system module.

请注意,‘continuing execution’ 和同步读取日志出现在异步读取内容之前。这表明了异步操作的非阻塞特性。

让我们使用带有回调的异步示例来探索一些关键的 fs 方法。请记住,在现代代码中通常首选基于 Promise 的版本(fs.promises)。

语法:fs.open(path, flags[, mode], callback)

  • path:文件路径。
  • flags:指定如何打开文件(例如,‘r’ 表示读取,‘w’ 表示写入,‘a’ 表示追加)。请参阅下表。
  • mode(可选):如果创建文件,设置文件权限(默认值:0o666)。
  • callback:调用时带参数 (err, fd) 的函数,其中 fd 是文件描述符(代表已打开文件的数字)。
标志 (Flag)描述
'r'以读取模式打开文件。如果文件不存在,会发生错误。
'r+'以读写模式打开文件。如果文件不存在,会发生错误。
'w'以写入模式打开文件。如果文件不存在则创建,如果存在则截断(清空内容)。
'w+'以读写模式打开文件。如果文件不存在则创建,如果存在则截断。
'a'以追加模式打开文件。如果文件不存在则创建。
'a+'以读写和追加模式打开文件。如果文件不存在则创建。

示例:

const fs = require('fs');
console.log('Going to open input.txt for reading...');
fs.open('input.txt', 'r', (err, fd) => {
if (err) {
return console.error('Error opening file:', err);
}
console.log('File opened successfully! File descriptor:', fd);
// 完成后记得关闭文件描述符
fs.close(fd, (closeErr) => {
if (closeErr) {
console.error('Error closing file:', closeErr);
}
console.log('File closed successfully.');
});
});

注意:使用 fs.readFile 或 fs.writeFile 等更高级别的函数通常更简单,因为它们会自动处理文件的打开和关闭。

语法:fs.stat(path, callback)

  • path:文件或目录的路径。
  • callback:调用时带参数 (err, stats) 的函数,其中 stats 是包含文件详细信息的 fs.Stats 对象。

stats 对象包含有用的属性(例如 size、mtime)和方法:

方法描述
stats.isFile()如果是常规文件,返回 true。
stats.isDirectory()如果是目录,返回 true。
stats.isSymbolicLink()如果是符号链接,返回 true。

示例:

const fs = require('fs');
console.log('Getting information for input.txt...');
fs.stat('input.txt', (err, stats) => {
if (err) {
return console.error('Error getting file stats:', err);
}
console.log('File Stats:', stats);
console.log('\nIs File?', stats.isFile());
console.log('Is Directory?', stats.isDirectory());
console.log('Size:', stats.size, 'bytes');
console.log('Last Modified:', stats.mtime);
});

语法:fs.writeFile(file, data[, options], callback)

  • file:文件路径。
  • data:要写入的数据(String、Buffer、TypedArray、DataView)。
  • options(可选):可以是编码字符串(默认 ‘utf8’)或一个对象 { encoding, mode, flag }(默认标志 ‘w’)。
  • callback:完成时调用,带参数 (err) 的函数。

如果文件存在,此方法会覆盖文件。使用标志 'a'(或 fs.appendFile)进行追加。

示例:

const fs = require('fs');
const content = 'Writing some data using fs.writeFile!';
console.log('Going to write to output.txt');
fs.writeFile('output.txt', content, 'utf8', (err) => {
if (err) {
return console.error('Error writing file:', err);
}
console.log('Data written successfully!');
// 通过回读来验证
fs.readFile('output.txt', 'utf8', (readErr, data) => {
if (readErr) {
return console.error('Error reading back file:', readErr);
}
console.log('Read back content: ' + data);
});
});

语法:fs.readFile(path[, options], callback)

  • path:文件路径。
  • options(可选):可以是编码字符串(例如,‘utf8’)或一个对象 { encoding, flag }。如果提供了编码,回调中的 data 将是一个字符串;否则,它将是一个 Buffer。
  • callback:调用时带参数 (err, data) 的函数。

示例(重温之前的异步示例):

const fs = require('fs');
console.log('Going to read input.txt');
fs.readFile('input.txt', 'utf8', (err, data) => {
if (err) {
return console.error('Error reading file:', err);
}
console.log('File content:\n' + data);
});

如果您省略编码,readFile 将返回一个 Buffer。Buffer 是 Node.js 处理二进制数据的方式。

const fs = require('fs');
fs.readFile('input.txt', (err, dataBuffer) => { // 未指定编码
if (err) {
return console.error(err);
}
console.log('Data as Buffer:', dataBuffer);
console.log('Data as String:', dataBuffer.toString('utf8'));
// 直接创建 Buffer(现代方式)
const bufFromArray = Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]); // 从字节数组创建
console.log(bufFromArray.toString()); // 输出: hello
const bufFromString = Buffer.from('world', 'utf8');
console.log(bufFromString);
});

注意:旧的 new Buffer() 构造函数由于安全问题已被弃用。请改用 Buffer.from()、Buffer.alloc() 或 Buffer.allocUnsafe()。

语法:fs.unlink(path, callback)

  • path:要删除文件的路径。
  • callback:调用时带参数 (err) 的函数。

示例(请谨慎使用!):

const fs = require('fs');
// 首先,创建一个要删除的文件
fs.writeFile('temp_to_delete.txt', 'Delete me', (err) => {
if (err) return console.error('Error creating temp file:', err);
console.log('Temporary file created.');
// 现在删除它
console.log('Going to delete temp_to_delete.txt');
fs.unlink('temp_to_delete.txt', (unlinkErr) => {
if (unlinkErr) {
return console.error('Error deleting file:', unlinkErr);
}
console.log('File deleted successfully!');
});
});

语法:fs.mkdir(path[, options], callback)

  • path:要创建目录的路径。
  • options(可选):可以是模式(权限,默认 0o777)或一个对象 { recursive, mode }。recursive: true 允许在需要时创建父目录(类似于 mkdir -p)。
  • callback:调用时带参数 (err) 的函数。

示例:

const fs = require('fs');
const path = require('path'); // 使用 path 模块以提高跨平台兼容性
const dirPath = path.join('.', 'my_new_directory', 'nested'); // 创建 './my_new_directory/nested'
console.log(`Going to create directory: ${dirPath}`);
// 使用 { recursive: true } 以便在需要时创建父目录
fs.mkdir(dirPath, { recursive: true }, (err) => {
if (err) {
return console.error('Error creating directory:', err);
}
console.log('Directory created successfully!');
});

语法:fs.readdir(path[, options], callback)

  • path:要读取目录的路径。
  • options(可选):编码(默认 ‘utf8’)或一个对象 { encoding, withFileTypes }。withFileTypes: true 返回 fs.Dirent 对象而不是仅文件名。
  • callback:调用时带参数 (err, files) 的函数,其中 files 是文件名(字符串)或 fs.Dirent 对象的数组。

示例:

const fs = require('fs');
console.log('Going to read contents of current directory (.)');
fs.readdir('.', { withFileTypes: true }, (err, files) => {
if (err) {
return console.error('Error reading directory:', err);
}
console.log('\nDirectory Contents:');
files.forEach(dirent => {
const type = dirent.isDirectory() ? 'Dir' : 'File';
console.log(` - ${dirent.name} (${type})`);
});
});

语法(fs.rmdir - 较旧,仅用于空目录):fs.rmdir(path, callback)

语法(fs.rm - 较新,推荐):fs.rm(path[, options], callback)

  • path:要移除目录的路径。
  • options(用于 fs.rm):一个对象 { recursive, force }。recursive: true 允许移除非空目录。
  • callback:调用时带参数 (err) 的函数。

示例(使用 fs.rm):

const fs = require('fs');
const path = require('path');
const dirToRemove = path.join('.', 'my_new_directory');
console.log(`Going to remove directory: ${dirToRemove}`);
// 使用 { recursive: true, force: true } 移除非空目录
// 谨慎使用 force,它会抑制一些错误。
fs.rm(dirToRemove, { recursive: true }, (err) => {
if (err) {
// 检查错误是否是由于目录不存在(这可能是预期情况)
if (err.code === 'ENOENT') {
console.log('Directory does not exist, nothing to remove.');
} else {
return console.error('Error removing directory:', err);
}
} else {
console.log('Directory removed successfully!');
}
});

fs 模块包含更多用于文件系统交互的函数(例如,重命名、更改权限、监视更改等)。有关完整列表和详细描述,请参阅 Node.js 官方文档: