image
node 的 fs 文档密密麻麻的 API 非常多, 毕竟全面支持对文件系统的操作. 文档组织的很好, 操作基本分为文件操作, 目录操作, 文件信息, 流这个大方面, 编程方式也支持同步, 异步和 Promise.
本文记录了几个文档中没详细描写的问题, 可以更好地串联 fs 文档思路:
文件描述符
同步, 异步与 Promise
目录与目录项
文件信息
stream
关注公众号 "心谭博客" / 前往 xxoo521.com / 欢迎交流和指正
文件描述符
文件描述符是一个非负整数. 它是一个索引值, 操作系统可以根据它来找到对应的文件.
在 fs 的很多底层 API 中, 需要用到文件描述符. 在文档中, 描述符通常用 fd 来代表. 例如: fs.read(fd, buffer, offset, length, position, callback). 与这个 API 相对应的是: fs.readFile(path[, options], callback).
因为操作系统对文件描述符的数量有限制, 因此在结束文件操作后, 别忘记 close:
- const fs = require("fs");
- fs.open("./db.json", "r", (err, fd) => {
- if (err) throw err;
- // 文件操作...
- // 完成操作后, 关闭文件
- fs.close(fd, err => {
- if (err) throw err;
- });
- });
同步, 异步与 Promise
所有文件系统的 API 都有同步和异步两种形式.
同步写法
不推荐使用同步 API, 会阻塞线程.
- try {
- const buf = fs.readFileSync("./package.json");
- console.log(buf.toString("utf8"));
- } catch (error) {
- console.log(error.message);
- }
异步写法
异步写法写起来容易进入回调地狱.
- fs.readFile("./package.json", (err, data) => {
- if (err) throw err;
- console.log(data.toString("utf8"));
- });
(推荐)Promise 写法
在 node v12 之前, 需要自己借助 promise 封装:
- function readFilePromise(path, encoding = "utf8") {
- const promise = new Promise((resolve, reject) => {
- fs.readFile(path, (err, data) => {
- if (err) return reject(err);
- return resolve(data.toString(encoding));
- });
- });
- return promise;
- }
- readFilePromise("./package.json").then(res => console.log(res));
在 node v12 中, 引入了 fs Promise API. 它们返回 Promise 对象而不是使用回调. API 可通过 require('fs').promises 访问. 如此一来, 开发成本更低了.
- const fsPromises = require("fs").promises;
- fsPromises
- .readFile("./package.json", {
- encoding: "utf8",
- flag: "r"
- })
- .then(console.log)
- .catch(console.error);
目录与目录项
fs.Dir 类: 封装了和文件目录相关的操作
fs.Dirent 类: 封装了目录项的相关操作. 例如判断设备类型 (字符, 块, FIFO 等).
它们之间的关系, 通过代码展示:
- const fsPromises = require("fs").promises;
- async function main() {
- const dir = await fsPromises.opendir(".");
- let dirent = null;
- while ((dirent = await dir.read()) !== null) {
- console.log(dirent.name);
- }
- }
- main();
文件信息
fs.Stats 类: 封装了文件信息相关的操作. 它在 fs.stat() 的回调函数中返回.
- fs.stat("./package.json", (err, stats) => {
- if (err) throw err;
- console.log(stats);
- });
注意, 关于检查文件是否存在:
不建议在调用 fs.open(), fs.readFile() 或 fs.writeFile() 之前使用 fs.stat() 检查文件是否存在. 而是应该直接打开, 读取或写入文件, 如果文件不可用则处理引发的错误.
要检查文件是否存在但随后并不对其进行操作, 则建议使用 fs.access().
ReadStream 与 WriteStream
在 Node.JS 中, stream 是个非常重要的库. 很多库的 API 都是基于 stream 来封装的. 例如下面要说的 fs 中的 ReadStream 和 WriteStream.
fs 本身提供了 readFile 和 writeFile, 它们好用的代价就是性能有问题, 会将内容一次全部载入内存. 但是对于几 GB 的大文件, 显然会有问题.
那么针对大文件的解决方案自然是: 一点点读出来. 这就需要用到 stream 了. 以 readStream 为例, 代码如下:
- const rs = fs.createReadStream("./package.json");
- let content = "";
- rs.on("open", () => {
- console.log("start to read");
- });
- rs.on("data", chunk => {
- content += chunk.toString("utf8");
- });
- rs.on("close", () => {
- console.log("finish read, content is:\n", content);
- });
借助 stream 的 pipe, 一行快速封装一个大文件的拷贝函数:
- function copyBigFile(src, target) {
- fs.createReadStream(src).pipe(fs.createWriteStream(target));
- }
参考链接
文件描述符
Socket 套接字
Node.JS 基础: stream 模块入门介绍与使用
Fastest way to copy file in node.JS
Using Node.JS to Read Really, Really Large Datasets & Files (Pt 1)
来源: http://www.jianshu.com/p/c27315453175