Node.js 內建模組之 fs
開啟掘金成長之旅!這是我參與「掘金日新計劃 · 2 月更文挑戰」的第 3 天,點選檢視活動詳情
fs(File system,檔案系統) 是 node 的一個內建模組,可用於在多種作業系統中(Windows 、macOS 等)對檔案進行操作。查閱 node 的官方文件,可以看到 File system 下有非常多的 API:
它們有的用於對檔案進行讀寫操作,有的用於對資料夾操作等,且都提供同步、非同步和 Promise 這 3 種操作方式。下面舉幾個常用的 API 為例,進行說明。
操作檔案
讀取檔案
同步讀取
fs.readFileSync()
用於同步 (Sync) 讀取檔案,直接傳入要讀取的檔案的路徑或檔案描述符即可:
javascript
// 程式碼塊 1.1.1
const fs = require('fs')
// 同步讀取
const resSync = fs.readFileSync('./test.txt')
console.log(resSync)
console.log('看看會不會阻塞')
執行結果如下,第 6 行的列印是在第 5 行之後,說明同步操作會阻塞後續程式碼的執行:
resSync
的結果是 Buffer 物件,本質上是二進位制的內容,只是以十六進位制來展示。如果想看到文字內容,可以對結果使用 toString()
進行轉換:console.log(resSync.toString())
,或者給 fs.readFileSync()
傳入第 2 個引數 —— 一個用於指定配置的可選物件,該物件有 2 個屬性:
encoding
,預設值為null
,也就是結果顯示為 buffer,想顯示文字可以改為'utf-8'
(或'utf8'
);flag
,讀取檔案時預設值為'r'
,更多 flag 相關資訊可檢視官方文件。javascript // 程式碼塊 1.1.2 const resSync = fs.readFileSync('./test.txt', { encoding: 'utf-8', flag: 'r' }) console.log(resSync) console.log('看看會不會阻塞')
現在看到的resSync
就為一個字串了:
非同步讀取 - Callback
fs.readFile()
用於非同步讀取檔案,其可以傳入 3 個引數,前兩個與 fs.readFileSync()
相同,第 3 個引數為一個回撥函式,當獲取到讀取結果時被呼叫:
javascript
// 程式碼塊 1.2.1
fs.readFile(
'./test.txt',
{
encoding: 'utf-8'
},
(err, data) => {
if (!err) console.log(data)
}
)
console.log('看看會不會阻塞')
執行結果如下,可以看到非同步讀取不會阻塞後續程式碼的執行:
非同步讀取 - Promise
fs.promises.readFile()
也是用於非同步讀取檔案,但可以避免在採用回撥函式的方式獲取結果時容易產生的回撥地獄:
javascript
// 程式碼塊 1.3.1
fs.promises
.readFile('./test.txt', {
encoding: 'utf-8'
})
.then(res => {
console.log(res)
})
.catch(err => console.log(err))
寫入檔案
寫入檔案也有同步非同步的方法,下面以非同步 - callback 的 api fs.writeFile()
為例。
其第 1 個引數可以是檔案的地址或檔案描述符,如果檔案不存在,則會進行建立;
第 2 個引數為要寫入的內容;
第 3 個引數為用於指定配置的可選物件,其中有之前介紹過的兩個屬性 encoding
和 flag
,只不過這裡 encoding
的預設值為 'utf8'
,flag
的預設值為 'w'
,即預設寫入的內容會覆蓋原有的內容,如果是想在原內容後追加寫入,則可以改為 'a'
(append);
第 4 個引數是一個回撥函式,在寫入操作執行結束後呼叫,如果寫入發生錯誤,就會把錯誤資訊作為引數傳入:
javascript
fs.writeFile(
'./test.txt',
'Hello Juejin',
{
encoding: 'utf8',
flag: 'w'
},
err => {
if (err) {
console.log(err)
return
}
console.log('寫入成功')
}
)
檔案描述符
前面說到,讀取檔案的這 3 種方法,傳入的第一個引數除了檔案的路徑外,還可以是檔案描述符(file descriptor)。在常見的作業系統中,核心對於每個程序都維護著一張當前開啟的檔案和資源的表格,而每個開啟的檔案都會被分配一個簡單的數字用於標識和跟蹤檔案,雖然不同的作業系統具體實現可能不同,但 node 幫我們處理了差異,為每個開啟的檔案分配了個數字型別的檔案描述符。
我們可以通過 fs.open()
開啟一個檔案來獲取檔案描述符:
javascript
// 程式碼塊 2.1.1
fs.open('./test.txt', (err, fd) => {
if (!err) {
console.log(fd)
}
})
檢視型別可以得知其第 2 個引數,也就是回撥函式會被傳入兩個引數,第 1 個為錯誤資訊 err
,第 2 個就是檔案描述符 fd
:
得到的結果為 3:
檢視檔案資訊
獲取到了檔案描述符,我們可以通過 fs.fstat()
(其第 1 個引數只能是 fd)檢視檔案資訊:
javascript
fs.open('./test.txt', (err, fd) => {
if (err) return
fs.fstat(fd, (err, stats) => {
if (err) return
console.log(stats)
fs.close(fd) // 關閉檔案
})
})
列印結果如下:
操作資料夾
建立資料夾
fs 模組還能對資料夾進行操作,比如 fs.mkdir()
就可以建立資料夾,mkdir 可以看成是 make directory 的縮寫:
javascript
fs.mkdir('./test', err => console.log(err)) // 建立 test 資料夾
讀取資料夾
fs.readdir()
可以用於讀取資料夾,在回撥函式中會返回讀取到的資料夾中所包含的檔案或資料夾,比如 node 目錄下的結構如下圖所示:
執行如下程式碼對 node 目錄進行讀取:
javascript
// fs.js
fs.readdir('../node', (err, files) => {
if (!err) console.log(files)
})
列印結果為:[ 'fs.js', 'test', 'test.txt' ]。
如果想獲取更詳細的資訊,可以傳入配置物件,將 withFileTypes
設定為 true
:
javascript
fs.readdir('../node', { withFileTypes: true }, (err, files) => {
if (!err) console.log(files)
})
那麼列印的結果就會通過 Symbol(type)
的值來表明是檔案(值為 1
),還是資料夾(值為 2
),關於 Symbol 的介紹可參看 Symbol 詳解:
現在還有個問題,就是 test 資料夾裡的 index.txt 檔案沒有被讀取到,如果想讀取 node 目錄下的所有檔案,可以通過遞迴讀取:
javascript
function readDirRecursive(path) {
fs.readdir(path, { withFileTypes: true }, (err, files) => {
if (err) return
files.forEach(item => {
if (item.isDirectory()) {
readDirRecursive(`${path}/${item.name}`)
} else {
console.log(item.name)
}
})
})
}
readDirRecursive('../node')
我們也需要將 withFileTypes
設定為 true
,這樣返回的 files
陣列中的元素才是一個個物件,並且有 name
屬性和 isDirectory()
方法可以判斷是否為資料夾。
重新命名資料夾/檔案
fs.rename()
可以重新命名資料夾或檔案,第 1 個引數為舊名稱,第 2 個引數傳新名稱即可:
javascript
fs.rename('./test', './juejin', err => console.log(err))
開啟掘金成長之旅!這是我參與「掘金日新計劃 · 2 月更文挑戰」的第 3 天,點選檢視活動詳情