| 文档创建人 | 创建日期 | 文档内容 | 更新时间 |
|---|---|---|---|
| adsionli | 2022-04-25 | node.js的fs模块 | 2022-04-25 |
最近一直在写图床项目,里面涉及到很多文件移动,创建,软链接的操作,所以需要用到node.js中的fs模块来进行文件系统的搭建,之前虽然也有简单的使用过,但是还是有很多的内部方法和属性还是不熟悉,所以这里记录与学习一下相关的内容。
fs模块常用方法
在fs模块中,每一个方法都被分成了同步调用以及异步调用,所以会看到有些方法后面会有xxxSync,其实使用是一样的,所以大家在学习和使用的时候不要混乱了
fs.Stats类
fs.Stats类主要是提供文件的相关信息的类
fs.Stats类是fs.stat(),fs.lstat(),fs.fstat()中的返回,它包括了我们需要的文件信息的详情。
fs.Stats类的主要参数说明:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| dev | number | 设备的数字标识符 |
| ino | number | 设备的索引号 |
| mode | number | 文件类型和模式 |
| nlink | number | 文件硬链接个数 |
| uid | number | 文件拥有者的标识符 |
| gid | number | 文件拥有的群组的标识符 |
| rdev | number | 数字型设备表标识符 |
| size | number | 文件大小(单位:字节) |
| blksize | number | 文件系统块的大小 |
| blocks | number | 文件系统为当前文件分配的块数 |
| atimeMs | number | 上次被访问时间(时间戳) |
| mtimeMs | number | 上次被修改的时间(时间戳) |
| ctimeMs | number | 上次更改文件状态的时间(时间戳) |
| birthtimeMs | number | 文件创建的时间(时间戳) |
| atime | string | 另一种形式的时间 |
| mtime | string | |
| ctime | string | |
| birthtime | string |
fs.Stats类的方法:
| 方法名 | 返回值 | 作用 |
|---|---|---|
| isFile | boolean | 是否是文件 |
| isDirectory | boolean | 是否是文件夹 |
| isBlockDevice | boolean | 是否是块设备(不知道是啥) |
| isCharacterDevice | boolean | 是否有特定字符描述字符设备(不知道是啥) |
| isSymbolicLink | boolean | 重要,在本系统中,是否是软链接或者说是符号链接,因为本项目中用的全是符号链接 |
| isFIFO | boolean | 是否是先进先出的管道(不知道是啥) |
| isSocket | boolean | 是否是socket通信文件(不知道是啥) |
上面有一些内容都需要去了解文件系统设计才能知道。。。我这里就偷懒不去了解了,就学习了一下自己必须要用的,就是isFile,isDirectory,isSymbolicLink,特别是最后一个,因为为了避免频繁的移动对系统的负担,所以选择了使用符号链接(软链接的形式)去设计文件的移动,因为这样快很多。
stat,lstat,fstat的区别
stat,lstat,fstat的区别实际也就是文件系统的三种读取方式
stat是最普通的一种读取方式,它接受的参数是一个真实有效的路径,对于其他情况都无法获取到相关文件内容,所以传入的路径必须是有效的
import fs from "fs"
//这个/usr/adsionli是一个真实路径,而不是符号链接
let stat = fs.stat("/usr/adsionli");
lstat是一种可以读取到真实路径也可以读取到符号路径的读取方式,所以这个lstat也被我选为主要使用的获取文件路径的方法,因为设计的时候的也是符号路径。其实就是平时我们创建的软链接,通过lstat也可以读取到内容。
import fs from "fs"
//这个/usr/adsionli/image是一个符号链接的路径,其真实路径为/public/image,但是通过lstat也可以读取到
let stat = fs.stat("/usr/adsionli");
fstat就比较特殊了,他不是通过文件路径来获取到的,而是通过文件描述符来指定的,文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。通过这个返回的文件描述符来获取文件信息。
import fs from "fs"
//先打开一个文件,然后通过返回的文件描述符再来获取文件详情
let fd = fs.openSync("/usr/adsionli", 'a');
let stat = fs.fstat(fd);
=当然最重要的还是我们的lstat,因为设计的时候使用时符号链接!!!!!! =
fs使用方法
这里需要注意的点就是,基本调用的都是同步方法,不太会使用异步方法,因为这些操作最好是在一次请求中等待完成的,或者是放入消息队列中去完成!
fs.mkdirSync
fs.mkdirSync是同步创建一个文件夹目录,在项目设计中是十分重要的,虽然用的是软链接,但是还是要把真实目录创建出来的,但是文件不需要一定挪移到相关目录下。
参数列表:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| path | string | 创建目录的名称 |
| mode | integer | 目录的读写权限(默认是0777) |
这里需要说明一点小坑的地方,使用mkdirSync或mkdir创建文件目录的时候,必须要一级一级进行创建,如果跨级创建,会报错的!
fs.accessSync
测试指定path路径下的文件或者目录的用户权限,这样说有点难以理解,其实就是可以用判断当前这个指定的路径内容能不能被查看、读取、写入、执行等功能的调用,所以可以用来让我们判断文件是否存在。
这个是没有返回值的,如果文件不存在就会抛出一个错误,所以要用try...catch(e)...去接一下
参数列表:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| path | string | 创建目录的名称 |
| mode | integer | 目录的读写权限(默认是fs.constants.F_OK) |
这里的mode有一些指定的值可以使用
| 值 | 作用 |
|---|---|
fs.constants.F_OK | 文件对调用进程可见。 这在确定文件是否存在时很有用,但不涉及 rwx 权限。 如果没指定 mode,则默认为该值。 |
fs.constants.R_OK | 文件可被调用进程读取 |
fs.constants.W_OK | 文件可被调用进程写入 |
fs.constants.X_OK | 文件可被调用进程执行。 对 Windows 系统没作用。 |
其实这里还可以使用fs.existSync来判断文件是否存在,同时注意:使用的一定是同步的方法。
为什么强调使用的必须是同步的方法,因为是异步方法的时候,可能在不同的处理文件的进程的时候,改变原文件的状态或者属性,导致读脏数据,就和数据库操作时,不加锁会导致脏数据的出现是一样的情况。
所以在node.js的文档中才会提到:
不建议在调用
fs.open()、fs.readFile()或fs.writeFile()之前使用fs.access()检查一个文件的可访问性。 如此处理会造成紊乱情况,因为其他进程可能在两个调用之间改变该文件的状态。 作为替代,用户代码应该直接打开/读取/写入文件,当文件无法访问时再处理错误。
fs.appendFileSync
追加数据到一个文件,如果文件不存在则创建文件。
其实在做分片文件合并的时候,有好几种方法可以完成,这里也可以采用fs.appendFileSync,对文件数据进行追加的形式,因为其接收的参数也可以是Buffer类型,而文件数据也可以被读成是Buffer数据。这样就可以不使用文件写入流也可以完成,当然两种都可以。
这里还有一点要说的就是,appendFileSync方法的写入是在文件的最后进行写入,这样就不会对之前的内容造成覆盖,这里也是我选用这个方法的原因
参数列表:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| file | string 、 Buffer 、 URL 、 number | 文件名或文件描述符 |
| data | string 、 Buffer | 写入的数据 |
| options | Object 、 string | 额外附加参数,具体下面给出 |
额外参数options内容说明:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| encoding | string 、 null | 数据编码方式,默认是utf8 |
| mode | integer | 文件权限设置,默认是0666 |
| flag | string | 默认值为a,指定附加到文件时使用的标志 |
使用示例
import fs from "fs"
//1. 传入一个文件路径
//注意,这里options是一个string,仅仅被用来指定输入的值的编码方式
fs.appendFileSync("/usr/adsionli/README.md", "adsionli是一个永远在学习代码路上的人!", "utf8");
//2. 传入的是一个文件
let fd = fs.openSync('/usr/adsionli/README.md', 'w');
fs.appendFileSync(fd, "adsionli永远爱老婆shirley", "utf8");
fs.openSync
非常重要的一个方法,用来打开一个文件,并且可以设置文件打开的flag,说明对文件的操作。
openSync有一个比较重要的参数就是第二参数flag,具体的可以看下面官方给出的:
-
'r'- 以读取模式打开文件。如果文件不存在则发生异常。 -
'r+'- 以读写模式打开文件。如果文件不存在则发生异常。 -
'rs+'- 以同步读写模式打开文件。命令操作系统绕过本地文件系统缓存。这对 NFS 挂载模式下打开文件很有用,因为它可以让你跳过潜在的旧本地缓存。 它对 I/O 的性能有明显的影响,所以除非需要,否则不要使用此标志。
注意,这不会使
fs.open()进入同步阻塞调用。 如果那是你想要的,则应该使用fs.openSync()。 -
'w'- 以写入模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。 -
'wx'- 类似'w',但如果path存在,则失败。 -
'w+'- 以读写模式打开文件。文件会被创建(如果文件不存在)或截断(如果文件存在)。 -
'wx+'- 类似'w+',但如果path存在,则失败。 -
'a'- 以追加模式打开文件。如果文件不存在,则会被创建。 -
'ax'- 类似于'a',但如果path存在,则失败。 -
'a+'- 以读取和追加模式打开文件。如果文件不存在,则会被创建。 -
'ax+'- 类似于'a+',但如果path存在,则失败。
之前一直对追加是什么玩意有点迷惑,后来看了一下,追加也是写的一种,追加是在文件的最后进行添加,所以是追加,如果是写文件的话,可能就得注意不要覆盖掉之前的内容了。
在 Linux 中,当文件以追加模式打开时,定位的写入不起作用。 内核会忽略位置参数,并总是附加数据到文件的末尾。
所以对文件进行合并的时候,我们可以进行追加操作。
openSync的第三个参数还是一个mode,但是这个mode只有在文件不存在时,并进行创建才会追加上去,相当于在内部调用了一个mkdir这种方法的时候写入的。
参数列表:
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| path | string 、 Buffer 、 URL | 文件路径 |
| flags | string 、 number | 文件打开的标志 |
| mode | integer | 文件权限设置,默认是0666 |
特殊说明:
fs.open()某些标志的行为是与平台相关的:在macOs和Linux下用'a+'标志打开一个目录(见下面的例子),会返回一个错误。所以尽量不要对目录打开使用追加的flag打开;如果是在在 Windows 和 FreeBSD,则会返回一个文件描述符。
fs.symlinkSync
全体起立!图床系统中最关键的一个内容,创建软链接(符号链接)
不过这里也有点不好的地方,如果原目标文件发生了变更,软链接的文件就找不到了,所以我们需要维护软链接文件,如果原目标文件发生改变,就去删除软链接文件,并且重新创建软链接。
也就是需要在使用一次
unlinkSync,去解除之前的软链接。
参数列表
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| target | string 、 Buffer 、 URL | 源文件,也就是需要创建软链接的文件 |
| path | string 、 Buffer 、 URL | 目标地址,软链接过去的地址 |
| type | string | 没啥用,主要是在windows系统上使用,用来说明文件是什么类型 |
使用例子:
import fs from "fs"
fs.symlinkSync("/usr/adsionli/README.md", "/code/adsionli_back/");
使用还是很简单的,但是如果要用好,就得自己在设计的时候仔细设置了,加油加油
fs.unlinkSync
也是核心使用函数之一,用来删除文件,也可以用来删除软链接,如果文件名称或者内容发生了变更的时候,就需要重新设置软链接了。
参数列表
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| path | string 、 Buffer 、 URL | 等待删除的文件,可以是符号链接的文件 |
使用例子:
//这里给出一个删除软链接的操作的例子
import fs from "fs"
let fd = fs.openSync("/usr/adsionli/README.md","r");
//获取软链接地址
let linkPath = fs.readlinkSync(fd);
fs.unlinkSync(linkPath);
//renameSync也可以进行文件移动
fs.renameSync("/usr/adsionli/README.md", "/usr/adsionli/hello.md");
fd = fs.openSync("/usr/adsionli/hello.md");
fs.symlinkSync("/usr/adsionli/hello.md", "/usr/adsionli_back/hello.md");
上面这个例子基本算是一个完整的对文件进行rename的过程了,所以这里还需大家根据自己的需求进行调整,而且这个过程可能会有点慢的,可以考虑放到消息队列中处理。
fs.renameSync
核心使用函数之一,在自己的图床项目中,用来控制文件的重命名操作。
参数列表
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| oldPath | string 、 Buffer 、 URL | 旧的路径 |
| newPath | string 、 Buffer 、 URL | 新的路径 |
使用例子:
import fs from "fs"
try {
fs.accessSync("/usr/adsionli/README.md", fs.constants.F_OK);
let fd = fs.openSync("/usr/adsionli/README.md", "r");
fs.renameSync(fd, "/code/adsionli_back/README.md");
}catch(e)}{
console.log(e);
}
fs.writeSync
也是一个比较重要的使用模块,用来写入文件,同时是同步写入
参数列表
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| fd | integar | 文件描述符,可以用open打开文件来获取 |
| buffer | Buffer 、 Uint8Array | 写入文件的二进制Buffer数据 |
| offset | integar | 写入Buffer的偏移量 |
| length | integar | 写入Buffer的长度 |
| position | integar | 偏移量,就是写入文件的位置 |
返回值
| 参数名 | 参数类型 | 参数说明 |
|---|---|---|
| bytesWritten | integar | 写入了的Buffer的数量(字节数量) |
| buffer | Buffer 、 Uint8Array | 写入的Buffer数据 |
具体使用的话,可以等待项目中使用了我再回来补充。
fs中的一些常量的记录
这里建议直接看文档,来得快fs中的常量
总结
除了上面这些内容之外,其实还涉及到一些关于写入流和读取流的相关知识内容,我将在另外一篇中去详细的说这两块内容,当然我会在自己用过之后才会写上去,因为需要使用过才能归纳出问题。
fs文件管理模块其实还需要配合path模块一起进行使用才能达到最佳效果,这些等我书写完成后,我会统一来处理,ヾ(◍°∇°◍)ノ゙加油加油!