【Node 基础】fs 模块

简介: 【Node 基础】fs 模块

原文来自 我的个人博客

前言

对于任何一个为服务端服务的语言或者框架来说,通常都会有自己的文件系统:

  1. 因为服务器需要将各种数据、文件等放置到不同的地方;
  2. 比如用户数据可能大多数都是放到数据库的
  3. 比如某些配置文件或者用户资源(图片、音视频)都是以文件的形式存在操作系统上

Node 也有自己的文件系统操作模块。就是 fs(File System)

借助于 Node 帮我们封装的文件系统,我们可以在任何的操作系统(window、Mac OS、Linus)上面直接去操作文件。这也是 Node 可以开发服务器的一大原因,也是它可以成为前端自动化脚本等热门工具的原因。

Node 文件系统的 API 非常多,这些 API大多数都提供了三种操作方式

  1. 同步操作文件:代码会被阻塞,不会继续执行;
  2. 异步回调函数操作文件:代码不会被阻塞,需要传入回调函数,当返回结果时,回调函数被执行;
  3. 异步 Promise 操作文件:代码不会被阻塞,通过 fs.promises 调用方法操作,会返回一个 Promise,可以通过 thencatcth 进行处理

本章我们呢会介绍最常用的几种 API。贴一下 文档地址,没介绍到的就去查文档吧。

1. 文件的读取

我们先来一个最简单的需求,如果我们想要读取一个文件,该怎么操作呢?

Node 给我们提供了三种读取文件的方式:

我们先创建一个 aaa.txt 的文件,随便写入一行 Hello World,接着调用 api 读取这个文件的内容。

1.1. readFileSync (同步读取)

第一种读取文件的方式是通过 readFileSync 同步读取文件:

// 引入 fs 模块
const fs = require("fs");

// 1. 同步读取
const file = fs.readFileSync("./aaa.txt");
console.log(file);
console.log("后续的代码");

image.png

可以看到打印的第一行的 Buffer 就是我们读取的文件了,后续代码在读取到文件后执行,说明了 readFileSync 确实是同步执行会阻塞后续代码的

关于 Buffer 我们后续再讲,如果想看到具体的文本,我们有两种方式实现。

  1. file 后加上 toString()
// 1.1. 同步读取 toString
const file1 = fs.readFileSync("./aaa.txt");
console.log(file1.toString());
console.log("后续的代码");
  1. readFileSync 第二个参数中添加 encoding: 'utf-8'
// 1.2. 同步读取 toString
const file2 = fs.readFileSync("./aaa.txt", {
  encoding: "utf-8",
});
console.log(file2);
console.log("后续的代码");

encoding 参数的作用是在读取文件时定义以什么样的编码读取文件,如果不传的话,它就会将文件当成一个二进制的文件(最终会转换为十六进制表示)

这两种方法最终都能打印出 Hello World

image.png

1.2 readFile(异步 + 回调函数)

第二种读取文件的方式是通过 异步 + 回调 的方式

// 2.1 异步 + 回调函数 readFile
fs.readFile("./aaa.txt", { encoding: "utf-8" }, (err, data) => {
  if (err) {
    console.error("读取文件错误", err);
    return
  }
  console.log(data);
});
console.log("后续的代码");

readFile 的前两个参数与 readFileSync 相同,它的第三个参数是一个回调函数,当读取到文件的时候会执行这个函数,这个函数有两个参数 errdata

image.png

可以看到先打印 “后续的代码”,说明 readFile 读取文件是没有阻塞后续代码的

1.3 readFile (异步 + Promise)

我们知道异步读取的结果如果通过回调的方式处理的话,非常容易产生回调地狱的问题。

自从有了 Pormise 后,Node 也支持了通过 Promise 的方式处理异步问题。

// 3.1 异步 + Promise
fs.promises
  .readFile("./aaa.txt", { encoding: "utf-8" })
  .then((res) => {
    console.log(res.toString());
  })
  .catch((err) => {
    console.error("读取文件错误", err);
  });
console.log("后续的代码");

fs.promises.readFilefs.readFile 的前两个参数都相同,只不过前者返回的是一个 Promise

1.4 总结

这一小节我们介绍了 fs 读取文件的三种方法,一种 同步,一种 异步+回调,一种 异步+PrimiseNode 中很多 API 都会有这三种方法,如果出现 fs.xxxfs.xxxSyncfs.promises.xxx 都是同理的大家都应该能明白了。

2. 文件描述符

什么是文件描述符(File descriptors)?

  1. 在常见的操作系统上,对于每个进程,内核都维护着一张当前打开着的文件的表格
  2. 在每个打开的文件都分配了一个称为文件描述符的简单的数字标识符
  3. 在系统层,所有文件系统操作都是用这些文件描述符来标识和跟踪每个特定的文件
  4. Windows 系统使用了一个虽然不同但概念上类似的机制来跟踪资源。

为了简化用户的工作,Node.js 抽象出操作系统之间的特定差异,并为所有打开的文件分配了一个数字型的文件描述符。

2.1 open 获取文件描述符

我们可以通过 fs.open() 方法是打开某个文件,为其分配文件描述符

const fs = require("fs");

fs.open("./aaa.txt", (err, fd) => {
  console.log(fd);
});

image.png

可以看到此时 aaa.txt 的文件描述符为 20

文件描述符,可用于从文件读取数据、写入数据、或请求相关文件的信息

2.2 文件描述符的使用

  1. 读取数据

上面讲的读取文件 API 的第一个参数不仅可以是一个路径,也可以是一个文件描述符,如下图所示:

image.png

const fs = require("fs");

// 可以把 1 当成是对 2 的封装

// 1
const file = fs.readFileSync('./aaa.txt')

// 2
fs.open("./aaa.txt", (err, fd) => {
  const file1 = fs.readFileSync(fd)
});
  1. 通过 fstat 请求文件相关信息
const fs = require("fs");

fs.open("./aaa.txt", (err, fd) => {

  fs.fstat(fd, (err, stats) => {
    if (err) return;
    console.log(stats);

    // 通过 open 打开的文件不会默认关闭掉,通常需要我们手动关闭
    fs.close(fd);
  });
});

上面的 stats 存储了一些文件的信息:

image.png

3. 文件的写入

有文件的读取,就会有写入

  1. 文件的读取:fs.readFile(path[,options],callback)
  2. 文件的写入:fs.writeFile(file,data[,options],callback)
const fs = require("fs");

let content = "aaaa";

fs.writeFile("./foo.txt", content, { encoding: "utf-8", flag: "a+" }, (err) => {
  console.log(err);
});

在上面的代码中,你会发现有一个对象类型 options,这个就是写入时填写的 option 参数:

  1. flag:写入的方式
  2. encoding:字符编码

3.1 flag 选项

flag 的值有很多,详见文档,这里介绍几种常见的:

  1. w 打开文件写入(默认)
  2. w+ 打开文件进行读写(可读可写),如果不存在则创建文件
  3. r 打开文件进行读取,读取时的默认值
  4. r+ 打开文件进行读写,如果不存在那么抛出异常
  5. a 打开要写入的文件,将流放在文件末尾。如果不存在则创建文件;
  6. a+ 打开文件进行读写(可读可写),将流放在文件末尾。如果不存在则创建文件

3.2 encoding 选项

常见的字符编码有:

  1. ASCII 编码:ASCII 码占用一个字节( 8 位),一共可以表示 256 个字符。
  2. GBXXXX 编码:为了显示中文设计的一套编码规则(著名的 GB2312
  3. GBK 编码:相比于 GB2312,多收录了包括古汉语、繁体、日语朝鲜语等汉字,在中文版Windows 操作系统下, 通常使用的编码方式都是 GBK 编码
  4. UnicodeUnicode统一码万国码单一码标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。
  5. UTF-8/-16/-32Unicode 和它们的关系可以看成 Unicode 是字符集,UTF-32/ UTF-16/ UTF-8是三种字符编码方案

    1. UTF-32UTF-32 又称UCS-4是一种将 Unicode 字符编码的协定,对每个字符都使用4字节。就空间而言,是效率非常低的,因此 UTF-32 使用并不广泛
    2. UTF-16:尽管有 Unicode 字符非常多,但是实际上大多数人不会用到超过前 65535 个之外的字,因此就有了 UTF-16(因为16位 = 2字节)
    3. UTF-8UTF-8(8-bit Unicode Transformation Format)是一种针对[Unicode]的可变长度字符编码,它使用一至四个字节为每个字符编码

4. 文件夹的操作

文件夹的操作都比较简单

4.1 mkdir 创建文件夹

文件夹的创建可以通过 mkdirmkdirSync

const fs = require("fs");

fs.mkdir("./coder/aaa", (err) => {
  console.log(err);
});

4.2 readdir 读取文件夹

  1. 文件夹的读取可以通过 readdirreaddirSync
fs.readdir("./coder", (err, files) => {
  console.log(err, files);
});

上面这段代码将会打印出 coder 文件夹下所有的 文件和文件夹的名称

image.png

  1. 如果想拿一下文件的类型,可以配置 optionwithFileTypestrue
fs.readdir("./coder", { withFileTypes: true }, (err, files) => {
  console.log(err, files);
  files.forEach((file) => {
    // isDirectory 判断是一个文件夹
    console.log(file.isDirectory());
  });
});

image.png

  1. 有了这个选项,我们就可以拿出所有文件夹下面的文件了。
function getAllFile(path) {
  const ret = [];
  function readDirectory(path) {
    const files = fs.readdirSync(path, { withFileTypes: true });
    files.forEach((file) => {
      // isDirectory 判断是否是一个文件夹
      if (file.isDirectory()) {
        readDirectory(`${path}/${file.name}`);
      } else {
        ret.push(file);
      }
    });
    
  }
  readDirectory(path);
  return ret;
}

console.log(getAllFile("./coder"));

创建以下结构的文件夹测试:

image.png

image.png

可以看到成功我们已经将所有文件都筛选了出来。

4.3 rename 文件夹重命名

fs.rename("./coder", "./coderyjw", (err) => {
  if (err) {
    console.log("重命名失败", err);
  }
});

rename 这个方法不仅可以重命名文件夹,也可以重命名文件

相关文章
|
3月前
|
分布式计算 JavaScript 前端开发
超级实用!详解Node.js中的lodash模块和async模块
超级实用!详解Node.js中的lodash模块和async模块
|
3月前
|
JSON JavaScript 前端开发
超级实用!详解Node.js中的util模块和express模块
超级实用!详解Node.js中的util模块和express模块
|
2月前
|
JavaScript
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
Node.js【GET/POST请求、http模块、路由、创建客户端、作为中间层、文件系统模块】(二)-全面详解(学习总结---从入门到深化)
27 0
|
14天前
|
域名解析 网络协议 JavaScript
【Node系列】node工具模块
Node.js有多个内置的工具模块,这些模块提供了用于执行各种任务的功能。
22 2
|
1月前
|
JavaScript 前端开发
Node.js之path路径模块
Node.js之path路径模块
|
1月前
|
JavaScript
Node.js之http模块
Node.js之http模块
|
2月前
|
资源调度 JavaScript 关系型数据库
Node.js【文件系统模块、路径模块 、连接 MySQL、nodemon、操作 MySQL】(三)-全面详解(学习总结---从入门到深化)
Node.js【文件系统模块、路径模块 、连接 MySQL、nodemon、操作 MySQL】(三)-全面详解(学习总结---从入门到深化)
33 0
|
3月前
|
Web App开发 JavaScript 前端开发
了解 Node.js 的运行机制:从事件循环到模块系统(下)
了解 Node.js 的运行机制:从事件循环到模块系统(下)
了解 Node.js 的运行机制:从事件循环到模块系统(下)
|
3月前
|
JavaScript 前端开发 数据挖掘
了解 Node.js 的运行机制:从事件循环到模块系统(上)
了解 Node.js 的运行机制:从事件循环到模块系统(上)
了解 Node.js 的运行机制:从事件循环到模块系统(上)
|
3月前
|
NoSQL JavaScript 数据库连接
超级实用!详解Node.js中的mongodb模块和socket.io模块
超级实用!详解Node.js中的mongodb模块和socket.io模块