![8535bc0d995dff200b4bb073f609dbb6.png](https://i-blog.csdnimg.cn/blog_migrate/78897573bde6b15e20c97b05a1a490a8.jpeg)
我努力找了找图,不过清晰度堪忧啊,所以自己做了一张,有点丑不过也就这样了,哈哈。
今天的内容主要就是 Node.js 的文件系统,毕竟服务器么,也有文件服务器不是?
1 文件系统模块
这里把文件系统模块放在前面来说,因为上次的 HTTP 模块相对简单,所以放在了最后,但是有关文件操作的模块通常都比较复杂,为了确保没接触过的朋友也能看明白,这次就把它放前面来了。
fs 模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互,它作为 Node.js 核心的组成部分,无需安装,可以通过简单地引用来使用它:
const
一旦这样做,就可以访问其所有的方法,包括:
- fs.access():检查文件是否存在,以及 Node.js 是否有权限访问
- fs.appendFile():追加数据到文件,如果文件不存在,则创建文件
- fs.chmod(): 更改文件(通过传入的文件名指定)的权限,相关方法:fs.lchmod()、fs.fchmod()
- fs.chown():更改文件(通过传入的文件名指定)的所有者和群组,相关方法:fs.fchown()、fs.lchown()
- fs.close():关闭文件描述符
- fs.copyFile():拷贝文件
- fs.createReadStream():创建可读的文件流
- fs.createWriteStream():创建可写的文件流
- fs.link():新建指向文件的硬链接
- fs.mkdir():新建文件夹
- fs.mkdtemp():创建临时目录
- fs.open():设置文件模式
- fs.readdir():读取目录的内容
- fs.readFile():读取文件的内容,相关方法:fs.read()
- fs.readlink():读取符号链接的值
- fs.realpath():将相对的文件路径指针(.、..)解析为完整的路径
- fs.rename():重命名文件或文件夹
- fs.rmdir():删除文件夹
- fs.stat():返回文件(通过传入的文件名指定)的状态,相关方法:fs.fstat()、fs.lstat()
- fs.symlink():新建文件的符号链接
- fs.truncate():将传递的文件名标识的文件截断为指定的长度,相关方法:fs.ftruncate()
- fs.unlink():删除文件或符号链接
- fs.unwatchFile():停止监视文件上的更改
- fs.utimes():更改文件(通过传入的文件名指定)的时间戳,相关方法:fs.futimes()
- fs.watchFile():开始监视文件上的更改,相关方法:fs.watch()
- fs.writeFile():将数据写入文件,相关方法:fs.write()
关于 fs 模块的特殊之处是,所有的方法默认情况下都是异步的,但是通过加上 Sync 后缀也可以同步地工作。
例如:
- fs.rename()
- fs.renameSync()
- fs.write()
- fs.writeSync()
这在应用程序流程中会产生巨大的差异。
从 Node.js 10 后, fs 也包括了对 Promise API 的支持,可以参考 FS Promise API。
例如,试验一下 fs.rename() 方法, 异步的 API 会与回调一起使用:
const
同步的 API 则可以这样使用,并使用 try/catch 块来处理错误:
const
此处的主要区别在于,在第二个示例中,脚本的执行会阻塞(!!很重要,有的时候反而是解决一些问题的思路),直到文件操作成功。
2 在 Node.js 中使用文件描述符
在与位于文件系统中的文件进行交互之前,需要先获取文件的描述符。
文件描述符是使用 fs 模块提供的 open() 方法打开文件后返回的:
const
注意,将 r 作为 fs.open() 调用的第二个参数,意味着打开文件用于读取。
当然如果仅仅是把文件描述符单独打印出来,其实就是个没什么意义的字符,比如:
![314ea219b2c2ec5bbdaf4468925ea727.png](https://i-blog.csdnimg.cn/blog_migrate/230e0f0c3e084ecea97b5ccaa4c9825b.png)
其他常用的标志有:
- r+ 打开文件用于读写
- w+ 打开文件用于读写,将流定位到文件的开头;如果文件不存在则创建文件
- a 打开文件用于写入,将流定位到文件的末尾;如果文件不存在则创建文件
- a+ 打开文件用于读写,将流定位到文件的末尾;如果文件不存在则创建文件
也可以使用 fs.openSync 方法打开文件,该方法会返回文件描述符(而不是在回调中提供):
const
一旦获得文件描述符,就可以以任何方式执行所有需要它的操作,例如调用 fs.open() 以及许多与文件系统交互的其他操作。
3 文件属性
每个文件都带有一组详细信息,可以使用 Node.js 进行检查,具体地说,使用 fs 模块提供的 stat() 方法。
调用时传入文件的路径,一旦 Node.js 获得文件的详细信息,则会调用传入的回调函数,并带上两个参数:错误消息和文件属性:
const
Node.js 也提供了同步的方法,该方法会阻塞线程,直到文件属性准备就绪为止:
const
文件的信息包含在属性变量中,可以通过属性提取很多信息,包括:
- 使用 stats.isFile() 和 stats.isDirectory() 判断文件是否目录或文件
- 使用 stats.isSymbolicLink() 判断文件是否符号链接
- 使用 stats.size 获取文件的大小(以字节为单位)
还有其他一些高级的方法,但是在日常编程中会使用的大部分是这些:
const
![a3eb63627e11e30cb0446fc44e89762a.png](https://i-blog.csdnimg.cn/blog_migrate/0d9bf6ffb546fb1688ea07a0a42a890c.png)
当然这里为了能够输出结果,我直接打印在了控制台,可以直接看到结果。
4 文件路径
系统中的每个文件都有路径。
在 Linux 和 macOS 上,路径可能类似于:
/users/joe/file.txt
在 Windows 上则有所不同,具有类似以下的结构:
C:usersjoefile.txt
当在应用程序中使用路径时需要注意,因为必须考虑到这种差异;不过好在 PowerShell 的出现使得统一使用类 Unix 路径成为可能,不过这个特指相对路径,如果是绝对路径,那么 Windows 还是必须遵从 [盘符]:[路径] 的方式。
可以使用以下方式将此模块引入到文件中:
const
现在可以开始使用其方法了。
4.1 从路径中获取信息
给定一个路径,可以使用以下方法从其中提取信息:
- dirname:获取文件的父文件夹
- basename:获取文件名部分
- extname:获取文件的扩展名
例如:
![7f4c6f6fca934be07fb6ecb90c243f2c.png](https://i-blog.csdnimg.cn/blog_migrate/3851f82e217126892e72dea8c40184df.png)
可以通过为 basename 指定第二个参数来获取不带扩展名的文件名:
![3ed386dc8deef03a186ad17256bf7fb5.png](https://i-blog.csdnimg.cn/blog_migrate/1a9c72f4ce10a76611e058d718027527.png)
4.2 使用路径
可以使用 path.join() 连接路径的两个或多个片段:
![02467fcf59db5e869ac666ed91ab5b0e.png](https://i-blog.csdnimg.cn/blog_migrate/641f515e46271761429331b1b8ecf3bf.png)
可以使用 path.resolve() 获得相对路径的绝对路径计算:
![08801e09daff7dcb6a77c8ed2f523720.png](https://i-blog.csdnimg.cn/blog_migrate/0ebf1c2244b187a3493d4179541a8d5b.png)
在此示例中,Node.js 只是简单地将 /server.js 附加到当前工作目录,如果指定第二个文件夹参数,则 resolve 会使用第一个作为第二个的基础:
![a81a90afbcc35e795bca2d73c8de7e1d.png](https://i-blog.csdnimg.cn/blog_migrate/8461f87860a9a015351e0937ca146897.png)
如果第一个参数以斜杠开头,则表示它是绝对路径:
![fbbfdaf5bb732ad2d0825a32f874ea11.png](https://i-blog.csdnimg.cn/blog_migrate/bc6bee9db289c7852d50a0b85c19c70c.png)
path.normalize() 是另一个有用的函数,当包含诸如 .、.. 或双斜杠之类的相对说明符时,其会尝试计算实际的路径:
![bc0f076783078414803bb7f29c57e6b0.png](https://i-blog.csdnimg.cn/blog_migrate/a55e575a18cb8afbf1dd3aeb9737203d.png)
解析和规范化都不会检查路径是否存在。 其只是根据获得的信息来计算路径。
5 读取文件
在 Node.js 中读取文件最简单的方式是使用 fs.readFile() 方法,向其传入文件路径、编码、以及会带上文件数据(以及错误)进行调用的回调函数:
![5ca8df19897fd7cf105a1da5108ad410.png](https://i-blog.csdnimg.cn/blog_migrate/f436e34f68ebc7bf0b50f5b43898b205.jpeg)
另外,也可以使用同步的版本 fs.readFileSync():
const
fs.readFile() 和 fs.readFileSync() 都会在返回数据之前将文件的全部内容读取到内存中。
这意味着大文件会对内存的消耗和程序执行的速度产生重大的影响。
在这种情况下,更好的选择是使用流来读取文件的内容。
6 写入文件
在 Node.js 中写入文件最简单的方式是使用 fs.writeFile() API。
例如:
![c14668872a0c5924393186de4d862563.png](https://i-blog.csdnimg.cn/blog_migrate/f1c70ec667d63242392bb90a669fa12d.png)
另外,也可以使用同步的版本 fs.writeFileSync():
const
默认情况下,此 API 会替换文件的内容(如果文件已经存在)。
可以通过指定标志来修改默认的行为:
fs
可能会使用的标志有:
- r+ 打开文件用于读写
- w+ 打开文件用于读写,将流定位到文件的开头;如果文件不存在则创建文件
- a 打开文件用于写入,将流定位到文件的末尾;如果文件不存在则创建文件
- a+ 打开文件用于读写,将流定位到文件的末尾;如果文件不存在则创建文件
(可以在 http://nodejs.cn/api/fs.html#fs_file_system_flags 中查看更多标志)
6.1 追加到文件
将内容追加到文件末尾的便捷方法是 fs.appendFile()(及其对应的 fs.appendFileSync()):
![1b855bb2e3ab8eafc625a9d626a841e9.png](https://i-blog.csdnimg.cn/blog_migrate/cf32408643e52c849cccec8f4cf4184e.png)
6.2 使用流
所有这些方法都是在将全部内容写入文件之后才会将控制权返回给程序(在异步的版本中,这意味着执行回调)。
在这种情况下,更好的选择是使用流写入文件的内容。
7 处理文件夹
Node.js 的 fs 核心模块提供了许多便捷的方法用于处理文件夹。
7.1 检查文件夹是否存在
使用 fs.access() 检查文件夹是否存在以及 Node.js 是否具有访问权限。
7.2 创建新的文件夹
使用 fs.mkdir() 或 fs.mkdirSync() 可以创建新的文件夹:
![d0cd19d6315823fc2ef4d680f1e678e1.png](https://i-blog.csdnimg.cn/blog_migrate/93c322d5d5c51b97a6bc249de3d56cb2.png)
7.3 读取目录的内容
使用 fs.readdir() 或 fs.readdirSync() 可以读取目录的内容。
这段代码会读取文件夹的内容(全部的文件和子文件夹),并返回它们的相对路径,因为同步方式比较简单,不需要太多代码,所以这个例子是同步的:
![5227675edf321d4355df00329fd9a48d.png](https://i-blog.csdnimg.cn/blog_migrate/3f4829d6980ad0b535eadb56c5a7e557.png)
可以获取完整的路径:
![4e77427e8054679c7c66a3c5a4233ec2.png](https://i-blog.csdnimg.cn/blog_migrate/14b8d168d47f3da43b375ed9618b962d.png)
也可以过滤结果以仅返回文件(排除文件夹):
![652d18d3090ee79ef62e78235f293d68.png](https://i-blog.csdnimg.cn/blog_migrate/a03340629216cd398728650ce1388872.png)
7.4 重命名文件夹
使用 fs.rename() 或 fs.renameSync() 可以重命名文件或者文件夹,第一个参数是当前的路径,第二个参数是新的路径:
![b14ee9af5c24a254e0c3ebfcd1d53ac0.png](https://i-blog.csdnimg.cn/blog_migrate/f6e17bdf33906e6696e594100edd81ca.png)
![edced858f951e78cc3d9c745067a899a.png](https://i-blog.csdnimg.cn/blog_migrate/18a7dc91c02cedd33fb1e000c6f0352f.png)
fs.renameSync() 是同步的版本:
const
7.5 删除文件夹
使用 fs.rmdir() 或 fs.rmdirSync() 可以删除文件夹。
删除包含内容的文件夹可能会更复杂,因为这个功能不能同时删除文件,所以必须先删除文件夹内的所有文件,才能实现删除文件夹的操作。
在这种情况下,最好安装 fs-extra 模块,该模块非常受欢迎且维护良好,它是 fs 模块的直接替代品,在其之上提供了更多的功能。
在此示例中,需要的是 remove() 方法。
首先使用以下命令安装该模块:
npm install -g fs-extra
并像这样使用它:
![7a0820b38d0edda8a5bffe249d6d4a63.png](https://i-blog.csdnimg.cn/blog_migrate/38f3fc1229fe2ea5708753afd3cfad16.png)
也可以与 promise 一起使用:
fs
或使用 async/await:
async
8 路径模块
因为前面用到了路径模块,所以也简单的介绍一下这个模块。
path 模块提供了许多非常实用的函数来访问文件系统并与文件系统进行交互,同样作为 Node.js 核心的组成部分,也是无需安装的,可以通过简单地引用来使用它:
const
该模块提供了 path.sep(作为路径段分隔符,在 Windows 上是 ,在 Linux/macOS 上是 /)和 path.delimiter(作为路径定界符,在 Windows 上是 ;,在 Linux/macOS 上是 :)。
还有一下这些 path 方法:
8.1 path.basename()
返回路径的最后一部分。 第二个参数可以过滤掉文件的扩展名:
![68e7519991829eb07812e8ba4d85f71c.png](https://i-blog.csdnimg.cn/blog_migrate/e53c283a7a055dea4129619275db7c5c.png)
8.2 path.dirname()
返回路径的目录部分:
![434a9b16c50b69cb5137ba4dc6641d46.png](https://i-blog.csdnimg.cn/blog_migrate/96d9ff1dd28bcd8665ef2b94e4be351a.png)
8.3 path.extname()
返回路径的扩展名部分:
![df6a9b007fa41c499133c9dc01b4995b.png](https://i-blog.csdnimg.cn/blog_migrate/5600dec97ff812c0de6da340221b9223.png)
8.4 path.isAbsolute()
如果是绝对路径,则返回 true:
![3300895dfd09657cefdf26de27504219.png](https://i-blog.csdnimg.cn/blog_migrate/56f047641c23b80c0ddb005b6dbe0b47.png)
8.5 path.join()
连接路径的两个或多个部分:
![e8cdf53854cef5ecc116b97535476529.png](https://i-blog.csdnimg.cn/blog_migrate/84b98a8dddf4f0556a09d342142f0bc4.png)
8.6 path.normalize()
当包含类似 .、.. 或双斜杠等相对的说明符时,则尝试计算实际的路径:
![ff9609113814e9774edba42faabdd7c3.png](https://i-blog.csdnimg.cn/blog_migrate/aab72cce68b84634073d73b122d76059.png)
8.7 path.parse()
解析对象的路径为组成其的片段:
- root:根路径
- dir:从根路径开始的文件夹路径
- base:文件名 + 扩展名
- name:文件名
- ext:文件扩展名
例如:
require
结果是:
![d5cea1ef0b7ef2f1e8f77aeb09d8340e.png](https://i-blog.csdnimg.cn/blog_migrate/6d822563eb1b7752e52048218d41dbec.png)
8.8 path.relative()
接受 2 个路径作为参数;基于当前工作目录,返回从第一个路径到第二个路径的相对路径。
例如:
![375367fb6e5fbe37ef17f929df13a497.png](https://i-blog.csdnimg.cn/blog_migrate/c422c988a443bda81a44fc6b10ece712.png)
8.9 path.resolve()
可以使用 path.resolve() 获得相对路径的绝对路径计算:
![31f49d620faa7e350e0accf7169e7142.png](https://i-blog.csdnimg.cn/blog_migrate/36c84c97254e83ed5412c6763f97349e.png)
通过指定第二个参数,resolve 会使用第一个参数作为第二个参数的基准:
![537950f23d9e1047dcf805d550c23cc0.png](https://i-blog.csdnimg.cn/blog_migrate/294d74f9455bba5b398a12d1896083b1.png)
如果第一个参数以斜杠开头,则表示它是绝对路径:
![aa1c92a3b242813f961ead340fbdb355.png](https://i-blog.csdnimg.cn/blog_migrate/42871254965399f7024a1f1ab36f2fb8.png)
当然 Windows 是无法省略掉盘符的,所以……不像类 Unix 系统是直接到了根路径。