# 文件和目录操作
# 什么是 fs 文件系统模块
fs 模块是 Node.js 官方提供的、用来操作文件的模块。它提供了一系列的方法和属性,用来满足用户对文件的操作需求。具体 API 可以看这里 (opens new window)。所有文件系统操作都具有同步、回调和基于 promise的形式。
- 回调的 API和Promise API,使用底层的 Node.js 线程池在事件循环线程之外执行文件系统操作。 这些操作不是同步的也不是线程安全的。 对同一文件执行多个并发修改时必须小心,否则可能会损坏数据。
- 同步的 API同步地执行所有操作,阻塞事件循环(和进一步的 JavaScript 执行),直到操作完成或失败。
- 当达到最佳性能时(无论是在执行时间还是在内存分配方面),fs 模块基于回调的 API 版本都比使用 promise API 更好(比同步也要快性能好)。但promise API可以解决回调地狱的问题。
基于 promise:
import { unlink } from "fs/promises";
try {
await unlink("/tmp/hello");
console.log("successfully deleted /tmp/hello");
} catch (error) {
console.error("there was an error:", error.message);
}
2
3
4
5
6
7
8
回调形式:
import { unlink } from "fs";
unlink("/tmp/hello", (err) => {
if (err) throw err;
console.log("successfully deleted /tmp/hello");
});
2
3
4
5
6
同步形式:
import { unlinkSync } from "fs";
try {
unlinkSync("/tmp/hello");
console.log("successfully deleted /tmp/hello");
} catch (err) {
// 处理错误
}
2
3
4
5
6
7
8
# 文件操作
# fs.open()
fs.open(path, flags[, mode], callback)
方法用于打开文件
path
,必选参数,表示文件的路径。flags
,文件打开的行为。请参阅文件系统标志。mode
,可选参数,设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。请参阅文件的模式callback
,回调函数,带有两个参数如:callback(err, fd)
。
# fs.close()
fs.close(fd, callback)
方法用于关闭文件
fd
:通过fs.open()
方法返回的文件描述符。callback
:回调函数。
# fs.read()
fs.read(fd, buffer, offset, length, position, callback)
从fd
指定的文件中读取数据
fd
:通过fs.open()
方法返回的文件描述符。buffer
:数据将写入的缓冲区。 默认值:Buffer.alloc(16384)
offset
:要写入数据的 buffer 中的位置。 默认值:0
length
:读取的字节数。 默认值:buffer.byteLength
position
:指定从文件中开始读取的位置。 如果 position 为null
或-1
,则将从当前文件位置读取数据,并更新文件位置。 如果 position 是整数,则文件位置将保持不变。callback
:回调函数,有三个参数callback(err, bytesRead, buffer)
。其中err
为错误信息,bytesRead
表示读取的字节数,buffer
为缓冲区对象。
import { open, close } from "fs";
open("myfile", "wx", (err, fd) => {
if (err) {
if (err.code === "EEXIST") {
console.error("myfile already exists");
return;
}
throw err;
}
const buf = Buffer.alloc(6);
console.log(buf);
fs.read(fd, buf, 0, 6, 24, (err, bytesRead, buffer) => {
if (err) {
console.error(err);
} else {
console.log("读取到的字节数:", bytesRead);
console.log("读取到的数据是:", buffer.toString());
console.log(buf);
}
close(fd, (err) => {
if (err) throw err;
});
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# fs.readFile()
fs.readFile(path[, options], callback)
方法,可以读取指定文件中的内容
path
,必选参数,表示文件的路径options
,可选参数,表示以什么编码格式来读取文件。如果不指定这个参数,默认会将读取到的数据放到 Buffer 中callback
,必选参数,文件读取完成后,通过回调函数拿到读取的结果
const fs = require("fs");
// err为报错信息,data为文件内容
fs.readFile("/Users/joe/test.txt", "utf8", (err, data) => {
if (err) {
console.error(`文件读取失败:${err.message}`);
return;
}
console.log(`文件读取成功:${data}`);
});
2
3
4
5
6
7
8
9
10
fs.readFile()
方法每次一块地异步读取文件内容到内存中,允许事件循环在每个块之间转换。 这允许读取操作对可能使用底层 libuv 线程池的其他活动的影响较小,但意味着将完整文件读入内存需要更长的时间。对于需要尽可能快地读取文件内容的应用程序,最好直接使用fs.read()
并让应用程序代码管理读取文件本身的全部内容。当然也可以使用fs.createReadStream()
以流的形式读取文件。
# fs.write()
fs.write(fd, buffer[, offset[, length[, position]]], callback)
将 buffer 缓冲区里的数据写入 fd 指定的文件。
fd
:通过fs.open()
方法返回的文件描述符。buffer
:数据源,是一个缓冲区。offset
:确定要写入的缓冲区部分length
:是整数,指定要写入的字节数。position
:指从文件开头数据应被写入的偏移量。 如果 typeof position !== 'number',则数据将写入当前位置。callback
:回调函数,有三个参数callback(err, bytesWritten, buffer)
。其中err
为错误信息,bytesWritten
表示写入的字节数,buffer
为缓冲区对象。
import { open, close } from "fs";
open("myfile", "wx", (err, fd) => {
if (err) {
if (err.code === "EEXIST") {
console.error("myfile already exists");
return;
}
throw err;
}
const buf = Buffer.from("hello");
console.log(buf);
fs.write(fd, buf, 0, 8, 0, (err, bytesWritten, buffer) => {
if (err) {
console.error(err);
} else {
console.log(bytesWritten);
console.log(buffer.toString());
console.log(buf);
}
close(fd, (err) => {
if (err) throw err;
});
});
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fs.write(fd, string[, position[, encoding]], callback)
将 string 写入 fd 指定的文件。
fd
:通过fs.open()
方法返回的文件描述符。string
:数据源,是一个字符串。position
指从文件开头数据应被写入的偏移量。 如果 typeof position !== 'number',则数据将写入当前位置。encoding
是预期的字符串编码。callback
:回调函数,有三个参数callback(err, written, string)
。其中err
为错误信息,written
表示写入的字节数。
fs.write()
在同一个文件上多次使用而不等待回调是不安全的。对于这种情况,推荐使用fs.createWriteStream()
(对于性能敏感的代码也推荐使用它)。
# fs.writeFile()
fs.writeFile(file, data[, options], callback)
方法,可以向指定的文件中写入内容。
file
,必选参数,需要指定一个文件路径的字符串,表示文件的存放路径data
,必选参数,表示要写入的内容options
,该参数是一个对象,包含{encoding, mode, flag}
。默认 encoding 为'utf8'
, mode 为0666
,flag
为'w'
callback
,必选参数,文件写入完成后的回调参数
const fs = require("fs");
fs.writeFile("message.txt", "Hello Node.js", "utf8", (err) => {
if (err) {
console.error(`文件写入失败:${err.message}`);
return;
}
console.log(`文件写入成功`);
});
2
3
4
5
6
7
8
9
fs.writeFile()
在同一个文件上多次使用而不等待回调是不安全的。对于这种情况,推荐使用fs.createWriteStream()
(对于性能敏感的代码也推荐使用它)。
# fs.stat()
fs.stat(path[, options], callback)
查看文件状态
const fs = require("fs");
fs.stat(__filename, function (err, stats) {
if (err) {
console.error(err);
return;
}
// birthtime文件创建时间。mtime文件内容发生变化,文件的修改时间
console.log(stats);
if (stats.isFile()) {
console.log("当前路径对应的是一个文件");
} else if (stats.isDirectory()) {
console.log("当前路径对应的是一个文件夹");
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
不推荐在调用fs.open()
、fs.readFile()
或fs.writeFile()
之前使用fs.stat()
检查文件是否存在。 而是,用户代码应该直接打开/读取/写入文件,并在文件不可用时处理引发的错误
stats 类中的方法有:
方法 | 描述 |
---|---|
stats.isFile() | 如果是文件返回 true,否则返回 false。 |
stats.isDirectory() | 如果是目录返回 true,否则返回 false。 |
stats.isBlockDevice() | 如果是块设备返回 true,否则返回 false。 |
stats.isCharacterDevice() | 如果是字符设备返回 true,否则返回 false。 |
stats.isSymbolicLink() | 如果是软链接返回 true,否则返回 false。 |
stats.isFIFO() | 如果是 FIFO,返回 true,否则返回 false。FIFO 是 UNIX 中的一种特殊类型的命令管道。 |
stats.isSocket() | 如果是 Socket 返回 true,否则返回 false。 |
# fs.access()
fs.access(path[, mode], callback)
检查文件是否存在而不对其进行操作。mode 参数是可选的整数,指定要执行的可访问性检查,可查阅文件访问的常量
import { access, constants } from "fs";
const file = "package.json";
// 检查当前目录中是否存在该文件。
access(file, constants.F_OK, (err) => {
console.log(`${file} ${err ? "does not exist" : "exists"}`);
});
// 检查文件是否可读。
access(file, constants.R_OK, (err) => {
console.log(`${file} ${err ? "is not readable" : "is readable"}`);
});
// 检查文件是否可写。
access(file, constants.W_OK, (err) => {
console.log(`${file} ${err ? "is not writable" : "is writable"}`);
});
// 检查当前目录中是否存在文件,是否可写。
access(file, constants.F_OK | constants.W_OK, (err) => {
if (err) {
console.error(`${file} ${err.code === "ENOENT" ? "does not exist" : "is read-only"}`);
} else {
console.log(`${file} exists, and it is writable`);
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
在调用fs.open()
、fs.readFile()
或fs.writeFile()
之前,不要使用fs.access()
检查文件的可访问性。 这样做会引入竞争条件,因为其他进程可能会在两次调用之间更改文件的状态。 而是,用户代码应直接打开/读取/写入文件,并处理无法访问文件时引发的错误。
“文件写入”的一种写法:
import { open, close } from "fs";
open("myfile", "wx", (err, fd) => {
if (err) {
if (err.code === "EEXIST") {
console.error("myfile already exists");
return;
}
throw err;
}
try {
writeMyData(fd);
} finally {
close(fd, (err) => {
if (err) throw err;
});
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
“文件读取”的一种写法:
import { open, close } from "fs";
open("myfile", "r", (err, fd) => {
if (err) {
if (err.code === "ENOENT") {
console.error("myfile does not exist");
return;
}
throw err;
}
try {
readMyData(fd);
} finally {
close(fd, (err) => {
if (err) throw err;
});
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# fs.appendFile()
fs.appendFile(path, data[, options], callback)
将数据追加到文件,如果该文件尚不存在,则创建该文件。 data 可以是字符串或 Buffer。
import { appendFile } from "fs";
appendFile("message.txt", "data to append", (err) => {
if (err) throw err;
console.log('The "data to append" was appended to file!');
});
2
3
4
5
6
import { open, close, appendFile } from "fs";
function closeFd(fd) {
close(fd, (err) => {
if (err) throw err;
});
}
open("message.txt", "a", (err, fd) => {
if (err) throw err;
try {
appendFile(fd, "data to append", "utf8", (err) => {
closeFd(fd);
if (err) throw err;
});
} catch (err) {
closeFd(fd);
throw err;
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# fs.createReadStream()
fs.createReadStream(path[, options])
- options
- flags:默认值
'r'
。请参阅文件系统标志。 - encoding:默认值
null
- fd:默认值
null
- mode:默认值
0o666
- autoClose:默认值
true
- emitClose: 默认值
true
- start:开始读取的位置
- end:默认值
Infinity
- highWaterMark:默认值
64 * 1024
- fs:默认值 null
- flags:默认值
# fs.createWriteStream()
# 文件的模式
mode 参数是使用以下常量的逻辑或创建的数字位掩码:
常量 | 八进制 | 描述 |
---|---|---|
fs.constants.S_IRUSR | 0o400 | 所有者可读取 |
fs.constants.S_IWUSR | 0o200 | 所有者可写入 |
fs.constants.S_IXUSR | 0o100 | 所有者可执行/搜索 |
fs.constants.S_IRGRP | 0o40 | 群组可读取 |
fs.constants.S_IWGRP | 0o20 | 群组可写入 |
fs.constants.S_IXGRP | 0o10 | 群组可执行/搜索 |
fs.constants.S_IROTH | 0o4 | 其他人可读取 |
fs.constants.S_IWOTH | 0o2 | 其他人可写入 |
fs.constants.S_IXOTH | 0o1 | 其他人可执行/搜索 |
构建 mode 的一种更简单的方法是使用三个八进制数字的序列(例如 765)。 最左边的数字(示例中的 7)指定文件所有者的权限。 中间的数字(示例中的 6)指定群组的权限。 最右边的数字(示例中的 5)指定其他人的权限。
数值 | 描述 |
---|---|
7 | 可读、可写和可执行 |
6 | 可读和可写 |
5 | 可读和可执行 |
4 | 只读 |
3 | 可写和可执行 |
2 | 只写 |
1 | 只可执行 |
0 | 无权限 |
例如,八进制值 0o765 表示:
- 所有者可以读取、写入和执行文件。
- 群组可以读取和写入文件。
- 其他人可以读取和执行文件。
在需要文件模式的地方使用原始数字时,任何大于 0o777 的值都可能导致特定于平台的行为不支持一致工作。 因此,像 S_ISVTX、S_ISGID 或 S_ISUID 这样的常量不会在 fs.constants 中暴露。
# 文件系统标志
以下标志在 flag 选项接受字符串的任何地方可用。
'a'
: 打开文件进行追加。 如果文件不存在,则创建该文件。'ax'
: 类似于'a'
但如果路径存在则失败。'a+'
: 打开文件进行读取和追加。 如果文件不存在,则创建该文件。'ax+'
: 类似于'a+'
但如果路径存在则失败。'as'
: 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。'as+'
: 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。'r'
: 打开文件进行读取。 如果文件不存在,则会发生异常。'r+'
: 打开文件进行读写。 如果文件不存在,则会发生异常。'rs+'
: 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。这主要用于在 NFS 挂载上打开文件,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。这不会将fs.open()
或fsPromises.open()
变成同步阻塞调用。 如果需要同步操作,应该使用类似fs.openSync()
的东西。'w'
: 打开文件进行写入。 创建(如果它不存在)或截断(如果它存在)该文件。'wx'
: 类似于'w'
但如果路径存在则失败。'w+'
: 打开文件进行读写。 创建(如果它不存在)或截断(如果它存在)该文件。'wx+'
: 类似于'w+'
但如果路径存在则失败。
# FS 常量
以下常量由 fs.constants 导出。并非每个常量都适用于每个操作系统。要使用多个常量,请使用按位或|
运算符。
import { open, constants } from "fs";
const { O_RDWR, O_CREAT, O_EXCL } = constants;
open("/path/to/my/file", O_RDWR | O_CREAT | O_EXCL, (err, fd) => {
// ...
});
2
3
4
5
6
7
# 文件访问的常量
以下常量旨在与fs.access()
一起使用。
常量 | 描述 |
---|---|
F_OK | 指示文件对调用进程可见的标志。 这对于确定文件是否存在很有用,但没有说明 rwx 权限。 未指定模式时的默认值。 |
R_OK | 指示文件可以被调用进程读取的标志。 |
W_OK | 指示文件可以被调用进程写入的标志。 |
X_OK | 指示文件可以被调用进程执行的标志。 这在 Windows 上不起作用(行为类似于 fs.constants.F_OK)。 |
# 文件复制的常量
以下常量旨在与fs.copyFile()
一起使用。
常量 | 描述 |
---|---|
COPYFILE_EXCL | 如果存在,如果目标路径已经存在,复制操作将失败并显示错误。 |
COPYFILE_FICLONE | 如果存在,复制操作将尝试创建写时复制引用链接。 如果底层平台不支持写时复制,则使用回退复制机制。 |
COPYFILE_FICLONE_FORCE | 如果存在,复制操作将尝试创建写时复制引用链接。 如果底层平台不支持写时复制,则操作将失败并显示错误。 |
# 文件打开的常量
以下常量旨在与fs.open()
一起使用。
常量 | 描述 |
---|---|
O_RDONLY | 指示打开文件以进行只读访问的标志。 |
O_WRONLY | 指示打开文件以进行只写访问的标志。 |
O_RDWR | 指示打开文件以进行读写访问的标志。 |
O_CREAT | 如果文件不存在则指示创建文件的标志。 |
O_EXCL | 如果设置了 O_CREAT 标志并且文件已经存在,则指示打开文件应该失败的标志。 |
O_NOCTTY | 标志表示如果路径标识一个终端设备,打开路径不应导致该终端成为进程的控制终端(如果进程还没有一个)。 |
O_TRUNC | 标志表示如果文件存在并且是一个普通文件,并且该文件被成功打开以进行写访问,则其长度应被截断为零。 |
O_APPEND | 指示数据将追加到文件末尾的标志。 |
O_DIRECTORY | 如果路径不是目录,则表示打开应该失败的标志。 |
O_NOATIME | 指示对文件系统的读取访问的标志将不再导致与文件关联的 atime 信息的更新。 此标志仅在 Linux 操作系统上可用。 |
O_NOFOLLOW | 如果路径是符号链接,则表示打开应该失败的标志。 |
O_SYNC | 指示文件为同步 I/O 打开的标志,写操作等待文件完整性。 |
O_DSYNC | 指示文件为同步 I/O 打开的标志,写操作等待数据完整性。 |
O_SYMLINK | 指示打开符号链接本身而不是它指向的资源的标志。 |
O_DIRECT | 设置后,将尝试最小化文件 I/O 的缓存影响。 |
O_NONBLOCK | 指示在可能的情况下以非阻塞模式打开文件的标志。 |
UV_FS_O_FILEMAP | 设置后,将使用内存文件映射来访问文件。 此标志仅在 Windows 操作系统上可用。 在其他操作系统上,此标志被忽略。 |
# 文件类型的常量
以下常量旨在与fs.Stats
对象的 mode 属性一起使用,以确定文件的类型。
常量 | 描述 |
---|---|
S_IFMT | 用于提取文件类型代码的位掩码。 |
S_IFREG | 常规文件的文件类型常量。 |
S_IFDIR | 目录的文件类型常量。 |
S_IFCHR | 面向字符的设备文件的文件类型常量。 |
S_IFBLK | 面向块的设备文件的文件类型常量。 |
S_IFIFO | FIFO/管道的文件类型常量。 |
S_IFLNK | 符号链接的文件类型常量。 |
S_IFSOCK | 套接字的文件类型常量。 |
# 文件模式的常量
以下常量旨在与fs.Stats
对象的 mode 属性一起使用,以确定文件的访问权限。
常量 | 描述 |
---|---|
S_IRWXU | 文件模式指示所有者可读、可写和可执行。 |
S_IRUSR | 文件模式指示所有者可读。 |
S_IWUSR | 文件模式指示所有者可写。 |
S_IXUSR | 文件模式指示所有者可执行。 |
S_IRWXG | 文件模式指示群组可读、可写和可执行。 |
S_IRGRP | 文件模式指示群组可读。 |
S_IWGRP | 文件模式指示群组可写。 |
S_IXGRP | 文件模式指示群组可执行。 |
S_IRWXO | 文件模式指示其他人可读、可写和可执行。 |
S_IROTH | 文件模式指示其他人可读。 |
S_IWOTH | 文件模式指示其他人可写。 |
S_IXOTH | 文件模式指示其他人可执行。 |