Node.js精進(6)——檔案

2022-06-29 09:01:17

  檔案系統是一種用於向用戶提供底層資料存取的機制,同時也是一套實現了資料的儲存、分級組織、存取和獲取等操作的抽象資料型別。

  Node.js 中的fs模組就是對檔案系統的封裝,整合了一套標準 POSIX 檔案 I/O 操作的集合,包括檔案的讀寫、刪除、遍歷、重新命名等操作。

  fs 模組中的所有方法都提供了三種形式:回撥、同步和 Promise ,其中 Promise 是在 Node.js 的版本 10 中引入的。

  本系列所有的範例原始碼都已上傳至Github,點選此處獲取。 

一、三種形式

  在回撥形式的方法中,最後一個引數是其回撥函數,會非同步地呼叫,其中回撥函數的第一個引數始終為異常預留,不過有個例外是 exists() 方法。

  回撥形式不容易書寫,很容易就會形成回撥地獄。

  雖然同步形式的方法比較容易書寫,但是在執行時會阻止 Node.js 事件迴圈和阻塞 JavaScript 執行,直到操作完成。

  Promise 形式的方法會使用底層的 Node.js 執行緒池,在事件迴圈執行緒之外非同步地執行檔案系統操作。對同一檔案執行多個並行修改時必須小心,有可能會損壞資料。

  以讀取檔案為例,三種形式的寫法如下所示,若不指定編碼,那麼輸出的將是 Buffer 範例。

const fs = require('fs');
// 回撥
fs.readFile('./data.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);    // strick
});

// 同步
const data = fs.readFileSync('./data.txt', 'utf8');
console.log(data);    // strick

// Promise
const { promises } = fs;
async function readFilePromise() {
  const data = await promises.readFile('./data.txt', 'utf8');
  console.log(data);    // strick
}
readFilePromise();

二、基礎使用

1)判斷檔案是否存在

  exists() 方法可用於判斷檔案是否存在,但在上一小節中曾提到,它已被棄用。

  這是因為此回撥的引數與其他回撥不一致。通常,Node.js 回撥的第一個引數是 err 引數,然後跟可選的其他引數,但 fs.exists() 回撥只有一個布林引數,如下所示。

fs.exists('./data.txt', isExist => {
  console.log(isExist);
});

  再則是因為 exists() 方法的功能用 access() 方法也能實現,其內部原始碼如下所示,其實也是呼叫了 access() 方法。

function exists(path, callback) {
  maybeCallback(callback);
  // 構造回撥函數
  function suppressedCallback(err) {
    callback(err ? false : true);
  }
  try {
    fs.access(path, F_OK, suppressedCallback);
  } catch {
    return callback(false);
  }
}

  其中 F_OK 是 fs 模組中的一個常數,表示檔案是否存在,使用方法如下所示,R_OK 表示是否可讀,W_OK 表示是否可寫。

const { constants } = require('fs');
const {  F_OK,  R_OK,  W_OK } = constants;

  注意,在呼叫 fs.open()、fs.readFile() 或 fs.writeFile() 之前,不能使用 fs.access() 檢查檔案是否存在。

  因為這樣做會引入競爭條件,其他程序可能會在兩次呼叫之間修改檔案狀態,造成非預期的結果。

  遇到這種場景,推薦的做法是直接開啟、讀取或寫入檔案,當檔案不可用時再做處理。

  另一種判斷檔案是否存在的方法是呼叫 stat(),讀取檔案屬性。

  它有兩個方法 isDirectory() 和 isFile() 可分別判斷是否是目錄和是否是檔案,如下所示。

fs.stat('./data.txt', (err, stats) => {
  console.log(stats.isDirectory());
  console.log(stats.isFile());
});

  同樣要注意的是,它也不能在呼叫 fs.open()、fs.readFile() 或 fs.writeFile() 之前,檢查檔案是否存在。

2)方法

  下面羅列的是 fs 模組的一些方法。

  • fs.open():開啟檔案,可設定檔案模式。
  • fs.close():關閉檔案描述符。
  • fs.createReadStream():建立可讀的檔案流。
  • fs.createWriteStream():建立可寫的檔案流。
  • fs.readFile():讀取檔案的內容,相關方法:fs.read()。
  • fs.writeFile():寫入檔案,相關方法:fs.write()。
  • fs.link():新建指向檔案的硬連結。
  • fs.unlink():刪除檔案或符號連結。
  • fs.mkdir():新建資料夾。
  • fs.rmdir():刪除資料夾。
  • fs.readdir():讀取目錄的內容。
  • fs.stat():讀取檔案屬性,相關方法:fs.fstat()、fs.lstat()。
  • fs.access():檢查檔案是否存在,以及 Node.js 是否有許可權存取。
  • fs.rename():重新命名檔案或資料夾。
  • fs.appendFile():追加資料到檔案,如果檔案不存在,則建立檔案。
  • fs.copyFile():拷貝檔案,可覆蓋檔案內容。
  • fs.chmod():更改檔案(通過傳入的檔名指定)的許可權,相關方法:fs.lchmod()、fs.fchmod()。
  • fs.chown():更改檔案(通過傳入的檔名指定)的所有者和群組,相關方法:fs.fchown()、fs.lchown()。
  • fs.watchFile():開始監控檔案的更改,相關方法:fs.watch()。
  • fs.unwatchFile():停止監控檔案的更改。

3)路徑

  路徑處理並不是在 fs 模組,而是在path模組,它的方法包括。

  • path.basename():讀取路徑的最後一部分。
  • path.dirname():讀取路徑的目錄部分。
  • path.extname():讀取路徑的副檔名。
  • path.isAbsolute():判斷是否是絕對路徑。
  • path.join():將多個部分合併成一個完整的路徑。
  • path.normalize():當包含類似 .、.. 或 // 等相對的說明符時,就嘗試計算實際的路徑。
  • path.parse():解析成路徑物件。
  • path.relative():基於當前目錄,返回從第一個路徑到第二個路徑的相對路徑。
  • path.resolve():將相對路徑計算成絕對路徑。
path.basename('../06/data.txt')    // data.txt
path.dirname('../06/data.txt');    // ../06
path.extname('../06/data.txt');    // .txt
path.isAbsolute('../06/data.txt');     // false
path.join('../', '06', 'data.txt');    // ../06/data.txt
path.normalize('/../06/data.txt');     // /06/data.txt
// { root: '', dir: '../06', base: 'data.txt', ext: '.txt', name: 'data' }
path.parse('../06/data.txt');
path.relative('../', '../06/data.txt');    // 06/data.txt
path.resolve('../06/data.txt');        // /Users/code/web/node/06/data.txt

 

參考資料:

判斷檔案存在

深入Node.js原始碼之檔案系統

Node.js官網檔案 API檔案系統

餓了麼File