深入Nodejs模塊fs - 文件系統操作


node 的fs文檔密密麻麻的 api 非常多,畢竟全面支持對文件系統的操作。文檔組織的很好,操作基本分為文件操作、目錄操作、文件信息、流這個大方面,編程方式也支持同步、異步和 Promise。

本文記錄了幾個文檔中沒詳細描寫的問題,可以更好地串聯fs文檔思路:

  • 文件描述符
  • 同步、異步與 Promise
  • 目錄與目錄項
  • 文件信息
  • stream

文件描述符

文件描述符是一個非負整數。它是一個索引值,操作系統可以根據它來找到對應的文件。

在 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

在 nodejs 中,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));
}

參考鏈接

最后

  1. 覺得不錯, 幫忙點個推薦唄,您的支持是對我最大的激勵
  2. 歡迎我的公眾號: 「心譚博客」,只專注於 前端 + 算法的原創分享

由於個人精力有限,很多系列和歷史文章沒有即時同步,請前往「前端圖譜」&「算法題解」,保證您有所收獲。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM