# fs 模块 - fs 文件系统,写入、读取、移动、删除、路径

TIP

从本节开始学习 NodeJS 的文件系统,fs 模块(也称之为 fs API)是 NodeJS 中非常重要的部分

  • 文件的写入
  • fs 文件读取
  • fs 文件重名、移动、删除、创建文件夹、查看资源状态、相对路径、绝对路径综合实践

# 一、文件的写入

TIP

深入浅出 NodeJS 的文件系统 - fs 模块(File System),文件的写入

# 1、fs 模块是什么

TIP

fs 模块可以实现与硬盘的交互,如:文件的创建、删除、重命名、移动,还有文件内容的写入、读取,以及文件夹的相关操作

因此,fs 是 NodeJS 中非常重要的一个模块,掌握 fs 模块后,对未来理解 NodeJS 相关程序时就会变得非常容易。

# 2、fs 文件写入

TIP

常用的文件写入方法如下

方法 描述
writeFile 异步写入
writeFileSync 同步写入
appendFile / appendFileSync 追加写入
createWriteStream 流式写入

# 2.1、writeFile 异步写入 - 语法

TIP

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

参数说明

  • file 文件名
  • data 待写入的数据
  • options 选项设置(可选)
  • callback 写入回调
  • 返回值:undefined

# 2.2、writeFile 异步写入 - 实践

TIP

需求:新建一个文件,文件名为:icoding.txt ,并在文件中写入内容 “用代码将梦想照进现实”

/src/writeFile.js

// 1、导入 fs 模块
// const 是声明符:可以是 const、let、var,一般会用 const
// fs 变量名称,自定义的(只要满足标识符的要求即可),一般都会使用模块名称
// require 为全局的函数,用来导入模块,不能随便写必须要写成 require
// require('模块名称')
const fs = require("fs");

// 2、写入文件
// 将 “用代码将梦想照进现实” 写入到当前文件夹下的 icoding.txt 文件中
fs.writeFile("./icoding.txt", "用代码将梦想照进现实", (err) => {
  // 如果写入失败,则回调函数调用时,会传入错误对象;如果写入成功,会传入 null
  // err 写入失败:错误对象,写入成功:null
  if (err) {
    console.log("写入失败 !", err);
    return;
  }
  console.log("写入成功 !");
});

启动 node 服务,成功在项目根目录中创建并将内容写入至 icoding.txt 文件中

node .\src\writeFile.js

# 2.3、fs 部分应用

TIP

  • 记录系统用户请求的日志(请求时间、IP、设备终端、停留时长 等)可实现自动化记录
  • 每次修改文件后,再次保存文件也是将内容再次写入到了文件中
  • VSCode 也是 NodeJS 开发的(Electron 框架),Electron 基于 NodeJS(作为后端运行时)和 Chromium(作为前端渲染),使得开发者可以使用 HTML、CSS 和 JavaScript 等前端技术来开发跨平台桌面 GUI 应用程序。
  • 而 VSCode 在写入文件时也是借助了 NodeJS 的 fs 模块实现的文件写入(在 VSCode 中写完代码,Ctrl + s 保存后,底层也用到了 fs 模块)

# 3、fs 异步 与 同步

TIP

  • 同步(Synchronous)

在同步编程中,程序的执行是按照顺序一个接一个地进行的。每个任务都必须在前一个任务完成之后才能开始执行。如果一个任务需要较长时间才能完成,那么整个程序将会被阻塞,无法继续执行其他任务。

  • 异步(Asynchronous)

在异步编程中,某些任务可以不必等待前一个任务完成就开始执行。这样可以使程序继续执行其他任务,而不会阻塞程序的执行。当异步操作完成时,程序会收到通知并返回到之前的任务继续执行。异步编程可以提高程序的效率和性能,特别是在处理耗时的任务时。

在异步编程中,通常会使用回调函数、Promises、async/await 等技术来处理异步操作。这些技术可以帮助开发者编写更加简洁、易读的异步代码,并更好地管理异步任务的执行和结果。

# 3.1、fs 异步写入 - writeFile

TIP

writeFile( ... ) 方法就是一种异步的工作模式

// 1、导入 fs 模块
const fs = require("fs");

// 2、写入文件
fs.writeFile("./icoding.txt", "用代码将梦想照进现实", (err) => {
  if (err) {
    console.log("写入失败 !", err);
    return;
  }
  console.log("写入成功 !");
});

代码解读

  • 代码从上至下开始运行,走到 fs.writeFile(...) 这块,它会进行磁盘的写入
  • 此时的磁盘写入,会交给另一个线程去完成
  • 即:目前有两个线程,一个是 JS 的主线程来执行 解析 JS 代码;第二个是 磁盘写入的线程,我们将这类线程统称为 IO 线程(输入 和 输出线程)
  • 启动 writeFile 磁盘写入线程后,writeFile 是异步的,异步是不会等待结果返回,直接向下运行后边的代码
  • IO 线程在写入完毕之后,它会将 回调函数 err => {} 压入到队列中,等待 JS 主线程把 JS 初始化的代码执行完毕之后,再把回调函数从队列中取出来再执行

在执行 fs.writeFile(...) 方法后,在加一行代码

// 1、导入 fs 模块
const fs = require("fs");

// 2、写入文件
fs.writeFile("./icoding.txt", "用代码将梦想照进现实", (err) => {
  if (err) {
    console.log("写入失败 !", err);
    return;
  }
  console.log("写入成功 !");
});

console.log(123 + 123);

在命令行终端中启动 node 服务并运行,

node .\src\writeFile.js

观察 writeFile 作为异步方法的执行顺序

image-20231130003639708

我们可以看到运行结果,是先执行了最后一行代码,然后再执行 回调函数 err => {} ,本质上就是上边描述的

image-20231130031155760

注:

  • 当代码执行到 fs.writeFile(...) 时,它会交给另一个线程去做磁盘写入
  • JS 主线程是不会等待结果,会直接执行后续的代码,因此先执行了 最后一行代码 console.log(123 + 123),先打印输出了结果 246
  • 待 IO 线程完成后,将回调函数 err => {} 压入到队列中,等 JS 主线程把其他初始化代码执行完毕之后,它再去任务队列中把回调函数取出来再执行,最后打印输出结果 写入成功 !

# 3.2、fs 同步写入 - writeFileSync

TIP

writeFileSync:同步写入,语法与 writeFile 类似,唯一不一样没有回调函数

语法:fs.writeFileSync(file, data[, options]) (opens new window)

参数说明

  • file 文件名
  • data 待写入的数据
  • options 选项设置(可选)

/src/writeFileSync.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、同步写入文件
fs.writeFileSync("./data.txt", "为每个互联网人提供高质量的终身学习平台");

启动 node 服务,成功在项目根目录中创建并将内容写入至 data.txt 文件中

node .\src\writeFileSync.js

writeFileSync 方法的运行原理(与 writeFile 完全不一样)

  • 代码从上至下运行,执行到 fs.writeFileSync(...) 时,IO 线程开始磁盘写入
  • 此时,JS 主线程就会停止了,会等待写完内容后,JS 主线程再往后执行

image-20231130025350655

# 3.3、同步 和 异步对比

TIP

以上同步和异步写入文件的方式中

  • 异步写入的效率会更高,在针对效率要求较高的场景中都会选择 writeFile(...) 异步写入的方法
  • 对性能要求不高的场景中,可使用 writeFileSync(...) 同步写入的方法
  • 同步 API 更易于理解,执行过程都是按照代码书写的顺序从上至下一行行执行的

# 4、fs 追加写入

TIP

fs 模块实现文件的追加写入,共有两个方法 appendFileappendFileSync。语法如下

返回值都为:undefined

# 4.1、fs 异步追加写入 - appendFile

TIP

需求:在 ./data.txt 文件内容的基础上追加一句话- 艾编程使命 (原内容是:"为每个互联网人提供高质量的终身学习平台")

/src/appendFile.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、调用 appendFile 异步追加写入
fs.appendFile("./data.txt", " - 艾编程使命", (err) => {
  if (err) {
    console.log("写入失败 !");
    return;
  }
  console.log("追加写入成功 !");
});

// 追加成功后,data.txt 文件中的内容变为:为每个互联网人提供高质量的终身学习平台 - 艾编程使命

# 4.2、fs 同步追加写入 - appendFileSync

/src/appendFile.js 中使用 appendFileSync 方法同步追加写入

// 1、导入 fs 模块
const fs = require("fs");

// 2、调用 appendFileSync 同步追加写入
// 追加的内容需换行,添加 \r\n
fs.appendFileSync("./data.txt", "\r\n同步追加写入的内容");

# 4.3、writeFile 实现追加写入

TIP

使用 fs.writeFile(...) 方法也可实现追加写入

// 1、导入 fs 模块
const fs = require("fs");

// 2、使用 writeFile 实现追加写入
// 注:需要添加一个参数 { flag: 'a' }
fs.writeFile(
  "./data.txt",
  "\r\n使用 writeFile 方法实现追加写入",
  { flag: "a" },
  (err) => {
    if (err) {
      console.log("写入失败 !");
      return;
    }
    console.log("写入成功 !");
  }
);

# 4.4、追加写入 - 应用场景

TIP

需要持续往文件中写入内容时,就需要用到追加写入文件的方法

  • 程序 log 日志
  • 记录系统用户访问的相关信息等

# 5、fs 流式写入

TIP

语法:fs.createWriteStream(path[, options]) (opens new window)

参数说明:

  • path 文件路径
  • options 选项配置(可选)

返回值:Object

# 5.1、流式写入应用

TIP

需要:在文件中写入一首诗

/src/createWriteStream.js

// 1、导入 fs 模块
const fs = require("fs");
// 2、创建流式写入对象,相当于与该文件建立一个通道,需要写入时就在通道中写入内容即可
let ws = fs.createWriteStream("./data.txt");

// 3、使用 write 方法写入
ws.write("《望庐山瀑布》\r\n");
ws.write("   唐·李白\r\n");
ws.write("日照香炉生紫烟,\r\n");
ws.write("遥看瀑布挂前川。\r\n");
ws.write("飞流直下三千尺,\r\n");
ws.write("疑是银河落九天。");

// 4、关闭通道(可选,即便不写此行代码,也会自动关闭通道)
ws.close();

# 5.2、流式写入 与 writeFile 写入的区别

TIP

  • 程序打开一个文件是需要消耗资源,流式写入可以减少打开关闭文件的次数
  • 流式写入方式适用于大文件写入或者频繁写入的场景
  • writeFile 适合于写入频率较低的场景

流式写入类似场景:

  • 我们请教老师问题,打开远程电脑桌面,我们一边操作一边问老师,这时为了提高通信的效率,远程桌面不挂断,即:流的通道不断开。这样就会非常方便了,这就是流式写入。
  • writeFile 写入:就是一次性的,每次写入文件时就与文件建立连接,本次写入完毕后就断开连接。下次再调 writeFile 还需要重新与文件建立连接,写入完成后再断开。

因此,流式写入更适合写入频繁的场景,而 writeFile 更适合写入频次相对较少的场景。同时也适合大文件的写入,后边会讲到。

# 6、文件写入的应用场景

TIP

文件写入在计算机中是一个常见的操作,如下

  • 下载文件,如:浏览器下载文件,发送请求至 -> 服务器 -> 服务器将文件信息返回 -> 浏览器 -> 浏览器接收到信息后,开始往文件中写入(可尝试下载文件观察下载的过程)
  • 安装软件,安装任意程序都是将程序写入硬盘的一个过程(如:C:\Program Files\Git
  • 保存程序日志,Git 日志(D:\web\icoding-web\.git\logs)、Nginx 日志 等
  • 编辑器保存文件,VSCode 打开一个文件,写入内容后 Ctrl + s 此时就会将内容写入文件中
  • 视频录制,录制完视频后会存储在对应的磁盘目录中
  • ... 等等

注:

  • 当 需要持久化保存数据时,就应该想到文件写入
  • 并不是每一次用文件写入都能实现我们的目标,但往这个方向思考肯定是没错的

# 二、fs 文件读取

TIP

文件读取:通过程序从文件中取出其中的数据

方法 描述
readFile 异步读取
readFileSync 同步读取
createReadStream 流式读取

# 1、异步读取 - readFile

TIP

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

参数说明:

  • path 文件路径
  • options 选项配置(可选)
  • callback 回调函数

返回值:undefined

# 1.1、readFile 异步读取应用

TIP

需求:读取 ./data.txt 文件(古诗),并在控制台中打印输出

/src/readFile.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、异步读取
// 回调函数有两个形参,参数名称自定义;
// 当读取到内容后该回调函数会被执行,并且会传入 err 和 data 两个值进来
// err:接收错误信息;data:接收读取的文件内容
fs.readFile("./data.txt", (err, data) => {
  if (err) {
    console.log("文件读取失败 !");
    return;
  }
  // 文件读取成功,直接打印输出内容
  console.log(data);
  // node 命令运行后,输出 Buffer
  // 打印输出:<Buffer e3 80 8a ... 99 e7 82 ... 91 more bytes>

  // 将 Buffer 转换成字符串,此时就成功读取古诗内容了
  console.log(data.toString());
});

运行后的结果

image-20231201005236926

# 2、同步读取 - readFileSync

TIP

语法:fs.readFileSync(path[, options]) (opens new window)

参数说明:

  • path 文件路径
  • options 选项配置(可选)

# 2.1、readFileSync 同步读取应用

/src/readFileSync.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、同步读取
const data = fs.readFileSync("./data.txt");
// 3、打印输出文件内容
console.log(data);
// node 命令运行后,输出 Buffer
// 打印输出:<Buffer e3 80 8a ... 99 e7 82 ... 91 more bytes>

// 将 Buffer 转换成字符串,此时就成功读取古诗内容了
console.log(data.toString());

运行后的结果

image-20231201010125595

# 3、读取文件的应用场景

TIP

  • 电脑开机:将磁盘中的内容读取至 -> 内存中 -> CPU 对程序处理运行
  • 程序运行:如 运行 QQ 音乐,打开后它其中的程序相关文件就会载入到 -> 内存中(从硬盘中读取文件 -> 载入内存中)
  • 编辑器打开文件:VSCode 点击打开一个文件,就能看到文件内容(本质上看到的文件内容都是从文件中读取出来的)
  • 查看图片,播放音乐、视频:如 双击打开一张图片,此时看图软件会先运行,将图片中的内容先读取、然后解析再呈现出来
  • 查看 Git 日志、Nginx 日志:git lognode logspm2 logs
  • 上传文件:百度网盘、阿里云盘(读取本地文件 -> 通过网络 -> 上传至服务器)
  • 查看聊天记录:微信、QQ(查看历史消息记录)

# 4、fs 流式读取 - createReadStream

TIP

流式读取的方式是一块一块的读取的,不是一次性全部读完

语法:fs.createReadStream(path[, options]) (opens new window)

参数说明:

  • path 文件路径
  • options 选项配置(可选)

# 4.1、createReadStream 流式读取应用

TIP

需求:将文件夹中的 ./mp4/Vue.mp4 文件读取出来,并载入内存

/src/createReadStream.js

// 1、导入 fs 模块
const fs = require("fs");
// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/vue.mp4");
// 3、为读取流对象 绑定 data 事件(用来获取读取到的数据)
// data 事件名称
// chunk 回调函数,chunk 自定义名称:块、大块
rs.on("data", (chunk) => {
  // 每当从文件中读取出一块数据后,就会执行一次回调
  // 并将读取到的内容传递给 形参 chunk,让其执行和处理
  // console.log(chunk)
  // node 命令运行后会输出很多 Buffer(文件内容)

  // 每次从文件中到底读取了多少内容呢 ?
  console.log(chunk.length); // 通过 length 获取 Buffer 的长度

  // 65536 => 64KB 即:流式读取每一次会从文件中读取 64KB 的数据
  // 最后一个是 59540 ,是因为不够 65536 了,即:剩余的数据
});

node 命令运行后会输出很多 Buffer,使用 length 属性查看每次读取文件内容的大小

image-20231201015525679

能否查看每次读取的文件内容呢 ?

// 1、导入 fs 模块
const fs = require("fs");
// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/vue.mp4");
// 3、为读取流对象 绑定 data 事件(用来获取读取到的数据)
// data 事件名称
// chunk 回调函数,chunk 自定义名称:块、大块
rs.on("data", (chunk) => {
  // 查看每次读取一块文件的内容
  console.log(chunk.toString());
});

启动 node 服务

node .\src\createReadStream.js

发现并没有得到我们想要的结果,原因是我们读取的是 mp4 的视频文件,每次读取的是流媒体的信息流,并不是我们想象的 UTF-8 的字符信息。

我们强行使用 .toString() 方法将视频流转换成字符信息,是看不到正常的文字结果

image-20231201020045736

# 4.2、end 事件

TIP

当读取流,将文件读取完毕后还会触发另一个事件 end

// 1、导入 fs 模块
const fs = require("fs");
// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/vue.mp4");
// 3、为读取流对象 绑定 data 事件(用来获取读取到的数据)
rs.on("data", (chunk) => {
  // 每次从文件中到底读取了多少内容呢 ?
  console.log(chunk.length); // 通过 length 获取 Buffer 的长度
});

// 4、当读取流,将文件读取完毕后还会触发 end 事件
// end 事件(可选,不一定要绑定该事件)
rs.on("end", () => {
  console.log("读取完成 !");
});

启动 node 服务

node .\src\createReadStream.js

image-20231201021028691

注:

该读取方式,在读取大文件 或 处理大文件时,可提高整体的效率。具体如何提高效率后面会讲到,先知道即可。

# 5、fs 应用 - 复制文件

TIP

需求:复制 ./mp4/Vue.mp4 文件夹中的 Vue.mp4 文件

# 5.1、方式一:readFile

/src/copyReadFile.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、读取文件内容(同步)
let data = fs.readFileSync("./mp4/Vue.mp4");
// 3、写入文件 至 Vue-2.mp4 文件中
fs.writeFileSync("./mp4/Vue-2.mp4", data);

启动 node 服务,复制文件成功

node .\src\copyReadFile.js

image-20231201022110044

# 5.2、方式二:流式操作

/src/copyCreateReadStream.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/Vue.mp4");
// 3、创建写入流对象
const ws = fs.createWriteStream("./mp4/Vue-3.mp4");

// 4、为读取流绑定 data 事件,读一点写入一点
rs.on("data", (chunk) => {
  ws.write(chunk);
});

启动 node 服务,复制文件成功

node .\src\copyCreateReadStream.js

image-20231201023115300

# 5.3、两种方式对比

TIP

两种方式都可以实现文件的复制,哪一种更好呢 ?

第二种方式,流式操作会更好一些。原因是 它所占用的资源会更少

  • 方式一:readFile 是将文件的所有内容全部都一次性读取到了内存中,然后再写入到另一个文件中。Vue.mp4 文件是 119MB 即:至少要占据 119MB 的内存空间。
  • 方式二:流式读取,理想状态只需要 64KB 的内存空间就可以完成文件的复制(因为它每次只取 64KB的内容,取一点写入一点)

为什么理想状态是 64KB 呢 ?

因为一般情况下,文件的 读取速度 一般都会比 写入速度 要更快。将数据读取后,开始写;还没有等到第一块数据写完,第二块数据又读取进来了。理想状态只需要 64KB 的空间即可。

即使是这样的方式它所占用的内存空间也比整个文件占用的内存空间要小。

image-20231202000803183

文件复制的过程

# 5.4、readFile - 占用内存空间

/src/copyReadFile.js 中,通过代码实践整个过程,查看复制文件所占用的内存空间

// 1、导入 fs 模块
const fs = require("fs");
// NodeJS 内置模块,获取当前进程相关信息,通过该模块中的方法 获取内存
const process = require("process");

// 2、读取文件内容(同步)
let data = fs.readFileSync("./mp4/Vue.mp4");
// 3、写入文件 至 Vue-2.mp4 文件中
fs.writeFileSync("./mp4/Vue-2.mp4", data);

// 当前文件复制的内存使用量
console.log(process.memoryUsage());

在命令行终端中启动 node 服务并运行

node .\src\copyReadFile.js

image-20231203162909823

注:

占用内存空间单位转换

  • 152920064 字节(Byte)/ 1024 = 149336 KB
  • 149336 KB / 1024 = 145 MB

因此,以上代码运行完毕(即:文件复制完成)所占用的内存是 145 MB

# 5.5、createReadStream 流式读取 - 占用内存空间

/src/copyCreateReadStream.js 中,通过代码实践整个过程,查看复制文件所占用的内存空间

// 1、导入 fs 模块
const fs = require("fs");
// NodeJS 内置模块,获取当前进程相关信息,通过该模块中的方法 获取内存
const process = require("process");

// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/Vue.mp4");
// 3、创建写入流对象
const ws = fs.createWriteStream("./mp4/Vue-5.mp4");

// 4、为读取流绑定 data 事件,读一点写入一点
rs.on("data", (chunk) => {
  ws.write(chunk);
});

rs.on("end", () => {
  // 当前文件复制的内存使用量
  console.log(process.memoryUsage());
});

在命令行终端中启动 node 服务并运行

node .\src\copyCreateReadStream.js

image-20231203165759481

注:

占用内存空间单位转换

  • 66703360 字节(Byte)/ 1024 = 65140 KB
  • 65140 KB / 1024 = 63 MB

因此,以上代码运行完毕(即:文件复制完成)所占用的内存是 63 MB

# 5.6、两种方式 - 内存空间占用对比

TIP

以上两种方式实现文件的复制,明显可以看出流式读取的方式占用的内存空间会更少

流式读取占用内存空间 63MB < readFile 占用内存空间 145MB

当然,还有一种更简便的方式文件复制方法

/src/copyCreateReadStream.js 中使用 rs.pipe() 方法实现文件复制

// 1、导入 fs 模块
const fs = require("fs");

// 2、创建读取流对象
const rs = fs.createReadStream("./mp4/Vue.mp4");
// 3、创建写入流对象
const ws = fs.createWriteStream("./mp4/Vue-6.mp4");

// 使用 pipe() 方法,实现文件复制
// rs 读取流将文件读取完毕后 交给写入流 ws
// pipe 是管道的意思,即:将文件取出来 通过 管道 传递给写入流
// 该方法不常用,了解即可
rs.pipe(ws);

# 三、文件的其他操作

TIP

深入浅出文件文件重命名、文件移动、文件删除、文件夹操作,资源状态、文件路径相关、path 模块 等。

# 1、fs 文件重命名 和 移动

TIP

在 NodeJS 中,使用 renamerenameSync 来移动 或 重命名文件 或 文件夹

语法:

参数说明:

  • oldPath 文件当前路径
  • newPath 文件新路径
  • callback 操作后的回调函数

# 1.1、rename 异步文件重命名应用

TIP

需求:将 ./data.txt 文件重命名为 ./data-rename.txt

/src/rename.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、调用 rename 方法
// rename:重命名
fs.rename("./data.txt", "./data-rename.txt", (err) => {
  if (err) {
    console.log("重命名失败 !");
    return;
  }
  console.log("重命名成功 !");
});

在命令行终端中启动 node 服务并运行,成功实现文件的重命名

node .\src\rename.js

# 1.2、rename 文件移动

TIP

需求:将 ./data-rename.txt 文件移动到 ./assets 文件夹中

/src/fileMove.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、文件移动
fs.rename("./data-rename.txt", "./assets/data-rename.txt", (err) => {
  if (err) {
    console.log("文件移动失败 !");
    return;
  }
  console.log("文件移动成功 !");
});

在命令行终端中启动 node 服务并运行,成功实现文件的移动

node .\src\fileMove.js

注:

通过 rename 实现文件的重命名 和 文件的移动,本质上都是一样的:都是在更改文件的路径

# 2、fs 文件删除

TIP

在 NodeJS 中,使用 unlinkunlinkSync 来删除文件

语法:

参数说明:

  • path 文件路径
  • callback 删除后的回调函数

TIP

需求:删除 ./icoding.txt 文件

/src/unlink.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、调用 unlink 方法,删除文件
// unlink:翻译过来是 取消链接
fs.unlink("./icoding.txt", (err) => {
  if (err) {
    console.log("文件删除失败 !");
    return;
  }
  console.log("文件删除成功 !");
});

在命令行终端中启动 node 服务并运行,成功实现文件的删除

node .\src\unlink.js

# 2.2、rm 文件删除应用

TIP

rm 方法是 NodeJS v14.14.0 新增方法

语法:fs.rm(path[, options], callback) (opens new window)

/src/rm.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、调用 rm 方法,实现文件删除
fs.rm("./icoding.txt", (err) => {
  if (err) {
    console.log("文件删除失败 !");
    return;
  }
  console.log("文件删除成功 !");
});

注:

unlink 和 rm 方法都有对应的同步方法。即:unlinkSyncrmSync

# 3、fs 文件夹操作

TIP

在 NodeJS 中,可以对文件夹进行 创建、读取、删除 等操作

方法 描述
mkdir / mkdirSync 创建文件夹
readdir / readdirSync 读取文件夹
rmdir / rmdirSync 删除文件夹

# 3.1、mkdir 创建文件夹

TIP

在 NodeJS 中,使用 mkdirmkdirSync 来创建文件夹

语法:

参数说明:

  • path 文件夹路径
  • options 选项配置(可选)
  • callback 回调函数

# 3.2、mkdir 创建文件夹应用

TIP

需求:在项目根目录中创建一个文件夹 utils

/src/mkdir.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、创建文件夹(异步)
// mk:make 制作
// dir:directory 文件夹
fs.mkdir("./utils", (err) => {
  if (err) {
    console.log("文件夹创建失败 !");
    return;
  }
  console.log("文件夹创建成功 !");
});

在命令行终端中启动 node 服务并运行,成功实现文件夹的创建

node .\src\mkdir.js

# 3.3、递归创建文件夹

TIP

需求:在 ./utils 文件夹下再创建 ./utils/logs ... ./utils/logs/web

/src/mkdir.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、递归创建文件夹
// 需要添加配置参数 recursive 才能实现 递归创建
// recursive:递归
fs.mkdir("./utils/logs/web", { recursive: true }, (err) => {
  if (err) {
    console.log("文件夹创建失败 !");
    return;
  }
  console.log("文件夹创建成功 !");
});

在命令行终端中启动 node 服务并运行,成功实现递归文件夹的创建

node .\src\mkdir.js

如果需要文件夹展开显示,在 VSCode 中:设置 -> 搜索框输入 “compact” -> 去掉 “Explorer: Compact Folders” 勾选 即可展开纵向显示文件夹

image-20231204192757686

# 3.4、读取文件夹

TIP

需求:查看 ./mp4 文件夹下都有哪些资源

/src/readdir.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、读取文件夹
// read 读取 dir
// dir:directory 文件夹
fs.readdir("./mp4", (err, data) => {
  if (err) {
    console.log("读取失败 !");
    return;
  }
  // 读取成功,打印输出 mp4 文件夹中对应的资源
  console.log(data); // 目标文件夹中资源的名称列表
});

在命令行终端中启动 node 服务并运行,成功获取目标文件夹中资源的名称列表

node .\src\readdir.js

# 3.5、删除文件夹

TIP

需求:删除 ./test 文件夹

/src/rmdir.js 中,删除单个文件夹

// 1、导入 fs 模块
const fs = require("fs");

// 2、删除文件夹(单个文件夹)
// rm:remove 移除
fs.rmdir("./test", (err) => {
  if (err) {
    console.log("删除文件夹失败 !", err);
    return;
  }
  console.log("删除文件夹成功 !");
});

在命令行终端中启动 node 服务并运行,成功删除单个文件夹 test

node .\src\rmdir.js

# 3.6、递归删除

TIP

需求:删除 ./utils 文件夹中的所有 文件 和 文件夹(./utils/logs/web

/src/rmdir.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、递归删除,指定目录中的文件夹和文件
fs.rmdir("./utils", { recursive: true }, (err) => {
  if (err) {
    console.log("删除文件夹失败 !", err);
    return;
  }
  console.log("删除文件夹成功 !");
});

在命令行终端中启动 node 服务并运行,成功递归删除了指定目录中的文件夹和文件

node .\src\rmdir.js

注:

此时,命令行终端中有一个警告提示

  • 弃用警告:在 NodeJS 的未来版本中,fs.rmdir(path, { recursive: true }) 将被删除。请改用 fs.rm(path, { recursive: true })
  • 因此,递归删除建议使用 fs.rm(...),不推荐使用 fs.rmdir(...)

image-20231205002609096

# 3.7、rm 实现递归删除

/src/rmRecursive.js 中,递归删除 ./utils/logs/web/index.logs 文件夹 和 文件

// 1、导入 fs 模块
const fs = require("fs");

// 2、rm 实现递归删除,指定目录中的文件夹和文件
fs.rm("./utils", { recursive: true }, (err) => {
  if (err) {
    console.log("删除文件夹失败 !", err);
    return;
  }
  console.log("删除文件夹成功 !");
});

在命令行终端中启动 node 服务并运行,成功递归删除了指定目录中的文件夹和文件

node .\src\rmRecursive.js

此时,就不会再有任何警告提示信息了

image-20231205003722364

# 4、fs 查看资源状态

TIP

在 NodeJS 中,使用 statstatSync 查看资源的详细信息,state 状态的缩写

语法:

参数说明:

  • path 文件夹路径
  • options 选项配置(可选)
  • callback 操作后的回调函数

# 4.1、fs 查看资源状态应用

TIP

需求:查看 ./mp4/Vue.mp4 文件的状态

/src/stat.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、查看资源文件的状态
// stat:status 状态(缩写)
fs.stat("./mp4/Vue.mp4", (err, data) => {
  if (err) {
    console.log("查看状态失败 !", err);
    return;
  }
  // 查看状态成功,打印输出该资源的状态
  console.log(data);
});

在命令行终端中启动 node 服务并运行,成功输出资源文件的状态信息

node .\src\stat.js

命令行终端输出资源状态信息

image-20231205005229294

对应资源信息字段含义

  • dev:设备的 ID。这通常是在 Unix-like 系统中的设备标识符。
  • mode:文件的权限模式。这通常表示文件的读、写和执行权限。例如,33206 在 Unix-like 系统中通常表示文件具有读和执行权限,但没有写权限。
  • nlink:链接的数量。在 Unix-like 系统中,一个文件可以有多个硬链接。
  • uidgid:文件所有者和组 ID。在 Unix-like 系统中,这些值通常用于表示文件的所有者和所属的组。
  • rdev:如果是特殊文件(如设备文件或符号链接),则此字段包含原始设备的 ID。对于普通文件,此字段通常为 0。
  • blksize:文件系统分配给该文件的块的大小(以字节为单位)。
  • ino:文件的索引节点(inode)编号。inode 存储了文件的元数据(如权限模式、链接数量等),而实际的文件内容则存储在磁盘块中。
  • size:文件的大小(以字节为单位)。
  • blocks:文件占用的块的数量。在 Unix-like 系统中,文件通常存储在大小固定的块中。
  • atimeMsmtimeMsctimeMsbirthtimeMs:这些字段表示文件的访问时间、修改时间、状态更改时间和出生时间,单位为毫秒。注意,这些时间戳通常以本地时间表示。
  • atimemtimectimebirthtime:这些是相应时间戳的字符串表示形式,格式为 ISO 8601 日期时间字符串。

常用字段信息:size 文件大小、birthtime 文件创建时间、atime 文件最后访问时间、mtime 最后的修改时间、ctime 最后一次修改文件状态的时间

# 4.2、查看是否是文件 或 文件夹

/src/stat.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、查看资源文件的状态
// stat:status 状态(缩写)
fs.stat("./mp4/Vue.mp4", (err, data) => {
  if (err) {
    console.log("查看状态失败 !", err);
    return;
  }
  // 查看状态成功,打印输出该资源的状态
  // console.log(data)

  // 是否是 文件
  console.log(data.isFile()); // true
  // 是否是 文件夹
  console.log(data.isDirectory()); // false
});

注:

fs 查看资源状态信息 类似 在电脑中打开一个文件夹所显示的文件相关信息

# 5、fs 路径

TIP

fs 模块对资源进行操作时,路径的写法有两种

①、相对路径

  • ./data.txt 当前目录下的 data.txt
  • data.txt 等效于上面的写法
  • ../data.txt 当前目录的上一级目录中的 data.txt

②、绝对路径

  • D:/web/node Windows 系统下的绝对路径
  • /usr/bin Linux 系统下的绝对路径

相对路径中所谓的 当前目录,指的是 命令行的工作目录,而并非是文件的所在目录。所以当命令行的工作目录与文件所在目录不一致时,会出现一些 BUG

/src/twoPaths.js

// 1、导入 fs 模块
const fs = require("fs");

// 相对路径
// fs.writeFileSync('./data.txt', 'icodingedu')
// fs.writeFileSync('data.txt', 'icoding')
// fs.writeFileSync('../index.html', 'HTML')

// 绝对路径
// fs.writeFileSync('C:/data.txt', 'icodingedu')
fs.writeFileSync("/data.txt", "icodingedu"); // 在 Linux 中用的较多,Windows下效果也与以上一样(用的较少)

在命令行终端中启动 node 服务并运行,观察写入文件的位置

# 切换不同的工作目录运行

node .\src\twoPaths.js

# 将命令运行的目录切换 至 src
node .\twoPaths.js

注:

相对路径的参照物:命令行所在的工作目录,因此我们上边的代码运行后,跟我们的常识判断会有出入。

# 6、fs 相对路径 - 相关问题

TIP

由于相对路径的参照物是根据命令行所在的工作目录的变化而改变的,每次创建文件的目录就不稳定。这就造成了很大的困惑 !

此时,用绝对路径就可以解决这个问题

# 6.1、__dirname 绝对路径

TIP

__dirname 绝对路径:可以理解为它是一个 ”全局变量“,

作用:它始终保存的是所在文件的所在目录的绝对路径

/src/dirname.js

// 1、导入 fs 模块
const fs = require("fs");

// 绝对路径:它始终保存的是所在文件的所在目录的绝对路径
console.log(__dirname);

在命令行终端中启动 node 服务并运行,在不同的文件目录中,观察运行打印输出的路径

# 切换不同的工作目录运行

node .\dirname.js
node .\src\dirname.js

可以看到不论在哪个目录下运行,最终得到的路径是唯一不变的

image-20231205215817101

# 6.2、__dirname 绝对路径应用

TIP

拼接绝对路径,在任意目录都可成功运行

/src/dirname.js

// 1、导入 fs 模块
const fs = require("fs");

// 绝对路径:它始终保存的是所在文件的所在目录的绝对路径
// console.log(__dirname)
// 注:该方式拼接路径是不规范的,后续会讲到
fs.writeFileSync(__dirname + "/icoding.txt", "用代码将梦想照进现实");

在命令行终端中启动 node 服务并运行,在不同的文件目录中运行,都始终保存的是所在文件的所在目录中

# 切换不同的工作目录运行

node .\src\dirname.js
node .\dirname.js

注:

通过 __dirname 绝对路径拼接的方式,最终生成文件的目录就不会随着工作目录的变化而变化了。

因此,未来我们在使用 fs 相关模块进行操作时,都会加上 __dirname 绝对路径的方式,避免因为工作目录的变化而导致程序运行发生变化。

# 7、fs 模块综合实践

TIP

需求:将 /test 文件夹下的文件名称,批量重命名(将 1~9 的文件名前补 0)便于按文件名从小到大排序

image-20231206020036932

/src/fileRename.js

// 1、导入 fs 模块
const fs = require("fs");

// 读取 test 文件夹
const files = fs.readdirSync("./test");
// console.log(files)

// 遍历数组
files.forEach((item) => {
  // 拆分文件名
  let data = item.split("-");
  // console.log(data)

  // 分别取到数字 和 文件名
  let [num, name] = data;
  // console.log(num, name)

  // 判断,小于 10 的前边补 0
  if (Number(num) < 10) {
    num = "0" + num;
  }
  // 创建新的文件名
  let newName = num + "-" + name;
  // console.log(newName)

  // 重命名(旧名称, 新名称)
  fs.renameSync(`./test/${item}`, `./test/${newName}`);
});

在命令行终端中启动 node 服务并运行

node .\src\fileRename.js

# 8、path 模块

TIP

path 模块提供了用于处理文件和目录的路径的实用工具

官方 API 文档:path 模块 (opens new window)

以下是常用的 path 模块操作路径相关功能

功能 描述
path.resolve 拼接规范的绝对路径(常用)
path.sep 获取操作系统的路径分隔符
path.parse 解析路径并返回对象
path.basename 获取路径的基础名称
path.dirname 获取路径的目录名
path.extname 获得路径的扩展名

/src/path.js

// 1、导入 fs 模块
const fs = require("fs");

// 2、写入文件
fs.writeFileSync(__dirname + "/icoding.txt", "用代码将梦想照进现实");

// 打印输出拼接后的路径
console.log(__dirname + "/icoding.txt");

// 以上打印输出的结果:D:\web\node\icoding-node-http\src/icoding.txt

// 观察发现,以上路径的分隔符是不统一的
// 因此,我们就需要借助 path 模块的 resolve 方法来拼接规范的绝对路径

# 8.1、path.resolve 拼接规范的绝对路径

TIP

resolve 方法在实际开发中会比较常用

/src/path.js

// 1、导入 fs 模块
const fs = require("fs");
// 2、导入 path 模块
const path = require("path");

// 使用 path 模块的 resolve 方法来拼接规范的绝对路径
// 参数1:绝对路径
// 参数2:相对路径
// resolve 方法会将该 绝对路径 和 相对路径 做一个聚散,最后拼接出最终的绝对路径
const newPath = path.resolve(__dirname, "./icoding.txt");
// 打印输出,拼接后的绝对路径
console.log(newPath);

// 输出的结果:D:\web\node\icoding-node-http\src\icoding.txt
// 此时,得到的结果就是一个规范的 绝对路径了

注:

resolve 方法中的第 2 个参数是 相对路径,也可以写成 icoding.txt 最终输出的结果是一样的

# 8.2、path 模块其他方法

TIP

除 resolve 方法外的其他方法不常用,了解即可

/src/path.js

// 导入 path 模块
const path = require("path");

// sep 分隔符(获取操作系统的路径分隔符)
// Windows 打印输出:\ (反斜线)
// Linux 打印输出:/ (正斜线)
console.log(path.sep);

// parse 解析路径并返回对象

// 获取文件的绝对路径,打印输出:D:\web\node\icoding-node-http\src\path.js
// console.log(__filename)

// 定义文件路径(做转义,添加双反斜线)
let str = "D:\\web\\node\\icoding-node-http\\src\\path.js";
// 解析路径 并 返回对象
console.log(path.parse(str));

运行返回对象

image-20231206023745471

其他几个方法,快速获取文件名、文件所在目录、文件扩展名

/src/path.js

// 导入 path 模块
const path = require("path");

// 定义文件路径(做转义,添加双反斜线)
let str = "D:\\web\\node\\icoding-node-http\\src\\path.js";

// basename 获取路径的基础名称:快速获取文件名
console.log(path.basename(str)); // path.js

// dirname 获取路径的目录名
console.log(path.dirname(str)); // 输出:D:\web\node\icoding-node-http\src

// extname 获得路径的扩展名
console.log(path.extname(str)); // 输出:.js
上次更新时间: 2/2/2024, 11:15:37 AM

大厂最新技术学习分享群

大厂最新技术学习分享群

微信扫一扫进群,获取资料

X