文件系统管理与可视化模块开发-Part3-node.js之fs模块的学习与使用

456 阅读12分钟
文档创建人创建日期文档内容更新时间
adsionli2022-04-25node.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类的主要参数说明:

参数名参数类型参数说明
devnumber设备的数字标识符
inonumber设备的索引号
modenumber文件类型和模式
nlinknumber文件硬链接个数
uidnumber文件拥有者的标识符
gidnumber文件拥有的群组的标识符
rdevnumber数字型设备表标识符
sizenumber文件大小(单位:字节)
blksizenumber文件系统块的大小
blocksnumber文件系统为当前文件分配的块数
atimeMsnumber上次被访问时间(时间戳)
mtimeMsnumber上次被修改的时间(时间戳)
ctimeMsnumber上次更改文件状态的时间(时间戳)
birthtimeMsnumber文件创建的时间(时间戳)
atimestring另一种形式的时间
mtimestring
ctimestring
birthtimestring

fs.Stats类的方法:

方法名返回值作用
isFileboolean是否是文件
isDirectoryboolean是否是文件夹
isBlockDeviceboolean是否是块设备(不知道是啥)
isCharacterDeviceboolean是否有特定字符描述字符设备(不知道是啥)
isSymbolicLinkboolean重要,在本系统中,是否是软链接或者说是符号链接,因为本项目中用的全是符号链接
isFIFOboolean是否是先进先出的管道(不知道是啥)
isSocketboolean是否是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是同步创建一个文件夹目录,在项目设计中是十分重要的,虽然用的是软链接,但是还是要把真实目录创建出来的,但是文件不需要一定挪移到相关目录下。

参数列表:

参数名参数类型参数说明
pathstring创建目录的名称
modeinteger目录的读写权限(默认是0777)

这里需要说明一点小坑的地方,使用mkdirSync或mkdir创建文件目录的时候,必须要一级一级进行创建,如果跨级创建,会报错的!

fs.accessSync

测试指定path路径下的文件或者目录的用户权限,这样说有点难以理解,其实就是可以用判断当前这个指定的路径内容能不能被查看、读取、写入、执行等功能的调用,所以可以用来让我们判断文件是否存在。

这个是没有返回值的,如果文件不存在就会抛出一个错误,所以要用try...catch(e)...去接一下

参数列表:

参数名参数类型参数说明
pathstring创建目录的名称
modeinteger目录的读写权限(默认是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方法的写入是在文件的最后进行写入,这样就不会对之前的内容造成覆盖,这里也是我选用这个方法的原因

参数列表:

参数名参数类型参数说明
filestring 、 Buffer 、 URL 、 number文件名或文件描述符
datastring 、 Buffer写入的数据
optionsObject 、 string额外附加参数,具体下面给出

额外参数options内容说明:

参数名参数类型参数说明
encodingstring 、 null数据编码方式,默认是utf8
modeinteger文件权限设置,默认是0666
flagstring默认值为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这种方法的时候写入的。

参数列表:

参数名参数类型参数说明
pathstring 、 Buffer 、 URL文件路径
flagsstring 、 number文件打开的标志
modeinteger文件权限设置,默认是0666

特殊说明:

  1. fs.open() 某些标志的行为是与平台相关的:在macOs和Linux下用 'a+' 标志打开一个目录(见下面的例子),会返回一个错误。所以尽量不要对目录打开使用追加的flag打开;如果是在在 Windows 和 FreeBSD,则会返回一个文件描述符。

fs.symlinkSync

全体起立!图床系统中最关键的一个内容,创建软链接(符号链接)

不过这里也有点不好的地方,如果原目标文件发生了变更,软链接的文件就找不到了,所以我们需要维护软链接文件,如果原目标文件发生改变,就去删除软链接文件,并且重新创建软链接。

也就是需要在使用一次unlinkSync,去解除之前的软链接。

参数列表

参数名参数类型参数说明
targetstring 、 Buffer 、 URL源文件,也就是需要创建软链接的文件
pathstring 、 Buffer 、 URL目标地址,软链接过去的地址
typestring没啥用,主要是在windows系统上使用,用来说明文件是什么类型

使用例子:

 import fs from "fs"
 fs.symlinkSync("/usr/adsionli/README.md", "/code/adsionli_back/");

使用还是很简单的,但是如果要用好,就得自己在设计的时候仔细设置了,加油加油

fs.unlinkSync

也是核心使用函数之一,用来删除文件,也可以用来删除软链接,如果文件名称或者内容发生了变更的时候,就需要重新设置软链接了。

参数列表

参数名参数类型参数说明
pathstring 、 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

核心使用函数之一,在自己的图床项目中,用来控制文件的重命名操作。

参数列表

参数名参数类型参数说明
oldPathstring 、 Buffer 、 URL旧的路径
newPathstring 、 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

也是一个比较重要的使用模块,用来写入文件,同时是同步写入

参数列表

参数名参数类型参数说明
fdintegar文件描述符,可以用open打开文件来获取
bufferBuffer 、 Uint8Array写入文件的二进制Buffer数据
offsetintegar写入Buffer的偏移量
lengthintegar写入Buffer的长度
positionintegar偏移量,就是写入文件的位置

返回值

参数名参数类型参数说明
bytesWrittenintegar写入了的Buffer的数量(字节数量)
bufferBuffer 、 Uint8Array写入的Buffer数据

具体使用的话,可以等待项目中使用了我再回来补充。

fs中的一些常量的记录

这里建议直接看文档,来得快fs中的常量

总结

除了上面这些内容之外,其实还涉及到一些关于写入流和读取流的相关知识内容,我将在另外一篇中去详细的说这两块内容,当然我会在自己用过之后才会写上去,因为需要使用过才能归纳出问题。

fs文件管理模块其实还需要配合path模块一起进行使用才能达到最佳效果,这些等我书写完成后,我会统一来处理,ヾ(◍°∇°◍)ノ゙加油加油!