nodejs中的檔案系統

flydean發表於2021-01-17

簡介

nodejs使用了非同步IO來提升服務端的處理效率。而IO中一個非常重要的方面就是檔案IO。今天我們會詳細介紹一下nodejs中的檔案系統和IO操作。

nodejs中的檔案系統模組

nodejs中有一個非常重要的模組叫做fs。這個模組提供了許多非常實用的函式來訪問檔案系統並與檔案系統進行互動。

簡單統計一下,fs提供了下面這麼多種使用的檔案操作方法:

  • fs.access(): 檢查檔案是否存在,以及 Node.js 是否有許可權訪問。
  • fs.appendFile(): 追加資料到檔案。如果檔案不存在,則建立檔案。
  • fs.chmod(): 更改檔案(通過傳入的檔名指定)的許可權。相關方法:fs.lchmod()、fs.fchmod()。
  • fs.chown(): 更改檔案(通過傳入的檔名指定)的所有者和群組。相關方法:fs.fchown()、fs.lchown()。
  • fs.close(): 關閉檔案描述符。
  • fs.copyFile(): 拷貝檔案。
  • fs.createReadStream(): 建立可讀的檔案流。
  • fs.createWriteStream(): 建立可寫的檔案流。
  • fs.link(): 新建指向檔案的硬連結。
  • fs.mkdir(): 新建資料夾。
  • fs.mkdtemp(): 建立臨時目錄。
  • fs.open(): 設定檔案模式。
  • fs.readdir(): 讀取目錄的內容。
  • fs.readFile(): 讀取檔案的內容。相關方法:fs.read()。
  • fs.readlink(): 讀取符號連結的值。
  • fs.realpath(): 將相對的檔案路徑指標(.、..)解析為完整的路徑。
  • fs.rename(): 重新命名檔案或資料夾。
  • fs.rmdir(): 刪除資料夾。
  • fs.stat(): 返回檔案(通過傳入的檔名指定)的狀態。相關方法:fs.fstat()、fs.lstat()。
  • fs.symlink(): 新建檔案的符號連結。
  • fs.truncate(): 將傳遞的檔名標識的檔案截斷為指定的長度。相關方法:fs.ftruncate()。
  • fs.unlink(): 刪除檔案或符號連結。
  • fs.unwatchFile(): 停止監視檔案上的更改。
  • fs.utimes(): 更改檔案(通過傳入的檔名指定)的時間戳。相關方法:fs.futimes()。
  • fs.watchFile(): 開始監視檔案上的更改。相關方法:fs.watch()。
  • fs.writeFile(): 將資料寫入檔案。相關方法:fs.write()。

注意,上面fs提供的方法都是非同步的,所謂非同步的意思是,這些方法都提供了回撥函式,方便非同步觸發相應的處理邏輯。

我們舉一個簡單的讀取檔案的例子:

const fs = require('fs')

fs.readFile('/tmp/flydean.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data)
})

上面的例子中,我們從/tmp檔案中讀取了一個flydean.txt檔案。並在callback函式中分別對異常和正常的資料進行了處理。

fs在提供非同步方法的同時,還提供了同步的方法呼叫,這個同步的方法就是在非同步方法後面加上Sync:

const fs = require('fs')

try {
  const data = fs.readFileSync('/tmp/flydean.txt', 'utf8')
  console.log(data)
} catch (err) {
  console.error(err)
}

看下將上面的方法改寫成同步方法之後的樣子。

兩者的區別就是,同步方法會阻塞,一直等到file讀取完成。

Promise版本的fs

非同步操作怎麼能少得了Promsie, 因為fs中的操作都是非同步的,如果大家不想通過callback來使用fs的話,fs也提供了Promise版本。

還是剛剛的readfile的例子,我們看看如果使用Promise該怎麼處理:

const fs = require('fs/promises');

(async function(path) {
  try {
    await fs.readFile(path, 'utf8' );
    console.log(`讀取檔案成功 ${path}`);
  } catch (error) {
    console.error('出錯:', error.message);
  }
})('/tmp/flydean.txt');

fs的promise版本在fs/promises下面,上面的例子中我們使用了async和await,以同步的方式編寫非同步程式,非常的方便。

檔案描述符

檔案描述符就是指在nodejs中,當我們使用fs.open方法獲得的這個返回值。

我們可以通過這個檔案描述符來進步和檔案進行互動操作。

const fs = require('fs')

fs.open('/tmp/flydean.txt', 'r', (err, fd) => {
  //fd 是檔案描述符。
})

上面的open方法的第二個參數列示以只讀的方式開啟檔案。

我們看下常用的檔案系統標誌:

  • 'r': 開啟檔案用於讀取。 如果檔案不存在,則會發生異常。

  • 'r+': 開啟檔案用於讀取和寫入。 如果檔案不存在,則會發生異常。

  • 'w': 開啟檔案用於寫入。 如果檔案不存在則建立檔案,如果檔案存在則截斷檔案。

  • 'w+': 開啟檔案用於讀取和寫入。 如果檔案不存在則建立檔案,如果檔案存在則截斷檔案。

  • 'a': 開啟檔案用於追加。 如果檔案不存在,則建立該檔案。

  • 'a+': 開啟檔案用於讀取和追加。 如果檔案不存在,則建立該檔案。

當然,上面的例子也可以用openSync來改寫:

const fs = require('fs')

try {
  const fd = fs.openSync('/tmp/flydean.txt', 'r')
} catch (err) {
  console.error(err)
}

fs.stat檔案狀態資訊

nodejs提供了一個fs.Stats類,用來描述檔案的狀態資訊。

Stats提供了一些非常有用的方法來判斷檔案的狀態:

比如:
stats.isDirectory(),stats.isFile(),stats.isSocket(),stats.isSymbolicLink(),stats.ctime等。

stats還提供了一些關於檔案時間相關的選項:

  • atime "訪問時間" - 上次訪問檔案資料的時間。
  • mtime "修改時間" - 上次修改檔案資料的時間。
  • ctime "更改時間" - 上次更改檔案狀態(修改索引節點資料)的時間。
  • birthtime "建立時間" - 建立檔案的時間。

我們看一下怎麼獲取到fs.stat:

const fs = require('fs')
fs.stat('/tmp/flydean.txt', (err, stats) => {
  if (err) {
    console.error(err)
    return
  }

  stats.isFile() //true
  stats.isDirectory() //false
  stats.isSymbolicLink() //false
  stats.size //檔案大小
})

fs.Stats將會作為fs.stat的回撥函式引數傳入。通過fs.Stats,我們再進行一系列的操作。

fs的檔案讀寫

上面我們介紹了使用fs進行檔案讀取操作,下面我們來介紹怎麼使用fs來進行檔案寫入操作:

const fs = require('fs')

const content = 'www.flydean.com'

fs.writeFile('/tmp/flydean.txt', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //檔案寫入成功。
})

上面是一個callback版本的,我們再看一個同步版本的:

const fs = require('fs')

const content = 'www.flydean.com'

try {
  const data = fs.writeFileSync('/tmp/flydean.txt', content)
  //檔案寫入成功。
} catch (err) {
  console.error(err)
}

writeFile還支援一個額外的options引數,在options引數中,我們可以指定檔案寫入的flag標記位,比如:r+,w+,a,a+等等。

fs.writeFile('/tmp/flydean.txt', content, { flag: 'a+' }, err => {})

當然,除了使用a+表示append到檔案末尾之外,fs還提供了一個appendFile方法來向檔案末尾輸出:

const fs = require('fs')

const content = 'www.flydean.com'

fs.appendFile('/tmp/flydean.txt', content, err => {
  if (err) {
    console.error(err)
    return
  }
  //檔案append成功。
})

fs的資料夾操作

有檔案就有資料夾,fs提供了一系列的資料夾操作,比如:

mkdir,readdir,rename rmdir操作。

readdir相對而言負責點,我們舉例說明:

const fs = require('fs')
const folderPath = '/tmp'

fs.readdir(folderPath, function(err,files){
    if(err){
        console.log(err);
    }
    files.map(file => console.log(file));
})

fs.readdirSync(folderPath).map(fileName => {
    console.log(fileName);
})

上面的例子中,我們分別使用了readdir和readdirSync兩種方式來讀取目錄中的檔案。

大家可以看下其中的區別。

path操作

最後,我們介紹一個和file特別相關的path操作,它提供了一些實用工具,用於處理檔案和目錄的路徑。

path代表的是路徑。我們通過下面的方式來使用path:

const path = require('path')

為什麼需要path呢?我們知道這個世界上大約有兩種風格的作業系統,windows和POSIX。

在這兩種作業系統中,路徑的表達方式是不一樣的。所以,我們需要一個通用的path模組來為我們解決這個差異。

我們可以通過一個例子來觀察這個差異:

在windows上:

path.basename('C:\\temp\\myfile.html');
// 返回: 'myfile.html'

在POSIX上:

path.basename('C:\\temp\\myfile.html');
// 返回: 'C:\\temp\\myfile.html'

我們先來看一下path.basename這個方法,是用來返回path 的最後一部分。

上面的例子中,我們向windows傳入了一個windows風格的path,所以可以正常解析,得到正常的結果。

而在POSIX環境中,我們傳入了一個windows風格的路徑,無法正常解析,直接返回整個的結果。

path還有很多非常有用的方法,比如:

const notes = '/tmp/notes.txt'

path.dirname(notes) // /tmp
path.basename(notes) // notes.txt
path.extname(notes) // .txt

path.join('/', 'tmp', 'notes.txt') //'/tmp/notes.txt'

path.resolve('notes.txt') //'/Users/flydean/notes.txt' 從當前目錄開始解析,獲得相對路徑的絕對路徑

path.normalize('/tmp/flydean..//test.txt') ///tmp/test.txt  嘗試計算實際的路徑

本文作者:flydean程式那些事

本文連結:http://www.flydean.com/nodejs-file-system/

本文來源:flydean的部落格

歡迎關注我的公眾號:「程式那些事」最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

相關文章