Node.js?操作本地文件及深入了解fs內置模塊
前言
node.js
作為服務端應用,肯定少不了對本地文件的操作,像創(chuàng)建一個目錄、創(chuàng)建一個文件、讀取文件內容等都是我們開發(fā)中經常需要用到的功能
這篇文章我們將深入學習node
的內置模塊:fs
文件操作模塊,并使用它來操作本地文件,讓我們開始吧!
一、目錄操作
創(chuàng)建目錄
語法:
fs.mkdir(path[, options], callback)
參數(shù):
path
- 文件路徑options
配置對象,屬性有:recursive
是否以遞歸的方式創(chuàng)建目錄(創(chuàng)建嵌套目錄),默認為false
mode
設置目錄權限,默認為 0777( Windows 不支持)
callback
回調函數(shù),參數(shù)如下:err
錯誤信息path
僅在options
配置recursive
為true
時出現(xiàn),表示所創(chuàng)建的頂層目錄的絕對路徑
演示:
const fs = require("fs"); // 創(chuàng)建文件夾 fs.mkdir("./blog", (err) => { if (err) { console.log(err); } else { console.log("創(chuàng)建blog目錄成功!"); } });
我們無法直接創(chuàng)建嵌套的目錄:
想要創(chuàng)建嵌套目錄,需要配置options
對象:
// 創(chuàng)建嵌套文件夾 fs.mkdir("./blog/one", { recursive: true }, (err, path) => { if (err) { console.log(err); } else { console.log("創(chuàng)建blog目錄成功!"); console.log("path參數(shù)出現(xiàn)", path); } });
目錄重命名
語法:
fs.rename(oldPath, newPath, callback)
參數(shù):
- oldPath 老的名字
- newPath 新的名字
- callback 回調函數(shù),參數(shù)如下:
- err 錯誤信息
演示:
// 文件夾重命名 fs.rename("./blog", "./newBlog", (err) => { if (err) { console.log(err); } else { console.log("重命名成功!"); } });
讀取目錄
語法:
fs.readdir(path[, options], callback)
參數(shù):
path
目錄路徑options
可以是指定編碼格式的字符串,也可以是具有以下屬性的對象encoding
:指定編碼格式,默認值: ‘utf-8
’withFileTypes
files
數(shù)組是否包含<fs.Dirent>
對象,默認值:false
callback
回調函數(shù)err
:錯誤信息files
:目錄里的內容,數(shù)組格式
演示:
// 讀取目錄信息 fs.readdir("./newBlog", (err, data) => { if (err) { console.log("err", err); } else { // 數(shù)組結構:包含目錄下的所有文件名 console.log("data", data); } });
刪除目錄
語法:
fs.rmdir(path[, options], callback)
參數(shù):
path
- 文件路徑callback
回調函數(shù),參數(shù)如下:err
錯誤信息
演示:
注意: 在目錄里面有內容時,是無法直接刪除該目錄的,需要提前將目錄下的所有內容給刪掉,刪除文件會在下面講到:
二、文件操作
創(chuàng)建文件
使用fs.writeFile
方法創(chuàng)建一個文件并寫入內容,如果該文件本來就存在,則會替換文件原本的內容:
// 創(chuàng)建文件并寫入內容 // 需要提前有blog目錄 fs.writeFile("./blog/one.txt", "hello", (err) => { if (err) { console.log(err); } else { console.log("文件創(chuàng)建成功!"); } });
創(chuàng)建文件:
創(chuàng)建的內容:
注意: 使用writeFile
時,如要原本沒有需要創(chuàng)建的這個文件,則writeFile
會新建這個文件并向其中寫入指定內容,但如果原本有這個文件,則writeFile
會將原本的這個文件內容替換成我們指定的內容
我們可以在循環(huán)中批量寫入文件:
// 批量寫入文件 for (let i = 0; i < 10; i++) { fs.writeFile(`./blog/${i}.txt`, `blog-${i}`, (err) => { if (err) { console.log(err); } else { } }); }
追加文件內容
使用fs.appendFile
方法向一個文件內追加內容:
// 給文件追加內容 fs.appendFile("./blog/one.txt", "\nworld", (err) => { if (err) { console.log(err); } else { console.log("內容追加成功!"); } });
上面的代碼將在
blog
目錄下的one.txt
中另起一行追加world
的內容,\n
表示換行,同時也支持其它的轉義符號
讀取文件內容
使用fs.readFile
方法讀取文件內容:
// 讀取文件內容 fs.readFile("./blog/one.txt", (err, data) => { if (err) { console.log(err); } else { console.log(data); } });
默認讀取的內容是nodejs
的Buffer
數(shù)組格式,我們可以在獲取數(shù)據(jù)時通過toString
將其轉化成字符串:
也可以直接在讀取文件時指定讀取的編碼格式:
刪除文件
使用fs.unlink
方法刪除文件:
// 刪除文件; fs.unlink("./blog/one.txt", (err) => { if (err) { console.log(err); } else { console.log("刪除成功!"); } });
三、 讀取文件/目錄信息
使用fs.stat
可以用來獲取指定路徑的內容的詳細信息,包括文件大小、創(chuàng)建時間等:
// 讀取文件/目錄信息 fs.stat("./blog/one.txt", (err, stats) => { if (err) { console.log(err); } else { console.log("stats", stats); } });
回調函數(shù)中的stats
參數(shù)會接收到文件的詳細信息對象,其中各個屬性表示的含義如下:
Stats { // 包含文件的設備的數(shù)值型標識 dev: 641331036, // 描述文件類型和模式的位域 mode: 33206, // 文件的硬鏈接數(shù)量 nlink: 1, // 文件擁有者的數(shù)值型用戶標識 uid: 0, // 擁有文件的群組的數(shù)值型群組標識 gid: 0, // 如果文件表示設備,則為數(shù)字設備標識符 rdev: 0, // i/o 操作的文件系統(tǒng)塊大小 blksize: 4096, // 文件的文件系統(tǒng)特定的“inode”號 ino: 281474979034994, // 文件的字節(jié)大小 size: 12, // 分配給文件的塊的數(shù)量 blocks: 0, // 指示上次訪問此文件的時間戳 atimeMs: 1661836850563.044, // 指示該文件最后一次修改的時間戳 mtimeMs: 1661836850557.057, // 指示文件狀態(tài)最后一次更改的時間戳 ctimeMs: 1661836850557.057, // 指示此文件創(chuàng)建時間的時間戳 birthtimeMs: 1661836842506.9233, // 表示文件最后一次被訪問的時間 atime: 2022-08-30T05:20:50.563Z, // 表示文件最后一次被修改的時間 mtime: 2022-08-30T05:20:50.557Z, // 表示文件狀態(tài)最后一次被改變的時間 ctime: 2022-08-30T05:20:50.557Z, // 表示文件的創(chuàng)建時間 birthtime: 2022-08-30T05:20:42.507Z }
stats
還有如下的常用方法:
stats.isDirectory()
判斷該內容是否是一個目錄stats.isFile()
判斷該文件是否是一個常規(guī)文件
四、同步方法
上面三節(jié)所講的所有fs
的方法都是異步的,如下圖所示:
由于Node
環(huán)境執(zhí)行的JavaScript
代碼是服務器端代碼,所以絕大部分需要在服務器運行期反復執(zhí)行業(yè)務邏輯的代碼必須使用異步代碼
否則,同步代碼在執(zhí)行時期,服務器將停止響應,因為JavaScript
只有一個執(zhí)行線程
Sync同步方法
但node
也為我們提供了一些fs
的同步方法,我們只需在對應的方法名后加上Sync
即可使用它的同步方法:
fs.mkdirSync("./blog");
注意:fs
的同步方法沒有callback
(回調函數(shù))參數(shù),需要獲取的內容(如readdir
方法回調函數(shù)參數(shù)中的files
參數(shù)數(shù)據(jù))會通過函數(shù)return
的形式返回出去
大家可以自己動手試一下:在上面我們遇到過的
fs
的方法名加上Sync
后綴使其變成同步方法。這里就不一一舉例了
更多fs
同步的api
可見:nodejs官方文檔
捕捉錯誤
需要注意的是:如果我們使用同步寫法,一定要做好錯誤收集與處理!以防止服務端因同步方法報錯而導致宕機:
刪除不為空目錄的案例
在目錄操作中,我們了解到在目錄內容不為空時,我們是無法直接使用rmdir
刪除該目錄的
那么我們就需要先使用unlink
將目錄內的文件刪除掉,這里我們將使用同步方法實現(xiàn)一個通用函數(shù),來實現(xiàn)刪除任何指定的目錄,不管它內容為不為空:
const fs = require("fs"); function rmdirPlus(path) { try { // 讀取目錄內容 const dirData = fs.readdirSync(path); // 遍歷內容 dirData.forEach((item) => { // 獲取內容信息 const stats = fs.statSync(`${path}/${item}`); if (stats.isDirectory()) { // 如果該內容為目錄,則進行遞歸 rmdirPlus(`${path}/${item}`); } else { // 如果該內容不為目錄,直接刪除 fs.unlinkSync(`${path}/${item}`); } }); // 刪除目錄 fs.rmdirSync(path); } catch (error) { console.log("執(zhí)行錯誤!", error); } }
在服務端,如果沒有必要就盡量不要使用同步代碼!非必要不使用
服務器啟動時如果需要讀取配置文件,或者結束時需要寫入到狀態(tài)文件時,可以使用同步代碼,因為這些代碼只在啟動和結束時執(zhí)行一次,不影響服務器正常運行時的異步執(zhí)行
五、Promise方法
內置模塊fs
的所有異步方法都可以改寫成promise
的寫法,我們只需在引入fs
模塊時指定promises
后綴:
const fs = require("fs").promises;
之后使用fs
的方法就可以直接使用promise
的寫法了:
const fs = require("fs").promises fs.readFile('./blog/one.txt', 'utf-8').then(data => { console.log(data) })
讓我們使用promise
的寫法改寫一下上邊刪除不為空目錄的案例:
const fs = require("fs").promises; async function rmdirPlus(path) { try { // 讀取目錄內容 const dirData = await fs.readdir(path); await Promise.all( dirData.map(async (item) => { // 獲取內容信息 const stats = await fs.stat(`${path}/${item}`); if (stats.isDirectory()) { // 如果該內容為目錄,則進行遞歸 await rmdirPlus(`${path}/${item}`); } else { // 如果該內容不為目錄,直接return出去 return fs.unlink(`${path}/${item}`); } }) ); // 刪除目錄 await fs.rmdir(path); } catch (error) { console.log("執(zhí)行錯誤!", error); } }
這里巧妙的使用了map
方法和Promise.all
方法,并通過async
和await
來實現(xiàn)我們的需求
如果你需要同步使用fs
,推薦使用async
和await
來代替上面提到的Sync
同步方法!
因為
await
必須用在async
函數(shù)中,async
函數(shù)調用不會造成阻塞,它內部所有的await
阻塞都被封裝在一個Promise
對象中異步執(zhí)行
六、大文件操作
前面我們是通過readFile
方法來讀取文件內容,通過writeFile
和appendFile
來寫入文件內容,這些方法對文件數(shù)據(jù)的操作都是一次性操作,即一次性將數(shù)據(jù)讀出或一次性將數(shù)據(jù)寫入
在文件數(shù)據(jù)內容比較大時,這些方法的效率就會變得很慢,那有沒有什么效率高的方式呢?這就需要引入fs
模塊的stream
流了
stream流介紹
stream
是Node.js
提供的一個僅在服務區(qū)端可用的模塊,目的是支持“流”這種數(shù)據(jù)結構
什么是流? 流是一種抽象的數(shù)據(jù)結構
想象水流,當在水管中流動時,就可以從某個地方(例如自來水廠)源源不斷地到達另一個地方(比如你家的洗手池)
我們也可以把數(shù)據(jù)看成是數(shù)據(jù)流,比如你敲鍵盤的時候,就可以把每個字符依次連起來,看成字符流。這個流是從鍵盤輸入到應用程序,實際上它還對應著一個名字:標準輸入流(stdin
)
如果應用程序把字符一個一個輸出到顯示器上,這也可以看成是一個流,這個流也有名字:標準輸出流(stdout
)。流的特點是數(shù)據(jù)是有序的,而且必須依次讀取,或者依次寫入,不能像Array
那樣隨機定位
有些流用來讀取數(shù)據(jù),比如從文件讀取數(shù)據(jù)時,可以打開一個文件流,然后從文件流中不斷地讀取數(shù)據(jù)。有些流用來寫入數(shù)據(jù),比如向文件寫入數(shù)據(jù)時,只需要把數(shù)據(jù)不斷地往文件流中寫進去就可以了
讀取數(shù)據(jù)
在Node.js
中,讀取流(Readable
流) 也是一個對象,我們只需要響應流的事件就可以了:
data
事件表示流的數(shù)據(jù)已經可以讀取了end
事件表示這個流已經到末尾了,沒有數(shù)據(jù)可以讀取了error
事件表示出錯了
const fs = require("fs"); // 創(chuàng)建一個可讀的流 const rs = fs.createReadStream("./1.txt", "utf-8"); // 監(jiān)聽data事件,數(shù)據(jù)會一點一點的進行讀取 rs.on("data", function (chunk) { console.log("on", chunk); }); // 監(jiān)聽end事件,數(shù)據(jù)讀取完畢后觸發(fā) rs.on("end", function () { console.log("end"); }); // 監(jiān)聽error事件,出錯時觸發(fā) rs.on("error", function (err) { console.log("error", err); });
要注意,data
事件可能會有多次,每次傳遞的chunk
是流的一部分數(shù)據(jù)
寫入數(shù)據(jù)
要以流的形式寫入文件,只需要使用 寫入流(Writable
流) 不斷調用write()
方法,最后以end()
結束:
const fs = require("fs"); // 創(chuàng)建一個可寫的流 const ws = fs.createWriteStream("./2.txt", "utf-8"); // 寫入內容 ws.write("11111111111111111111"); ws.write("22222222222222222222"); ws.write("33333333333333333333"); ws.write("44444444444444444444"); ws.end();
管道(文件復制)
我們需要將一個大文件的內容復制到另一個大文件里,這就需要同時使用讀取流和寫入流,但我們自己使用兩者結合時,可能會無法控制某一方的速率,導致兩方速率不同步,使得最后復制過去的數(shù)據(jù)不完整
而node
貼心的為我們提供了一個pipe
管道(Readable
流的pipe()
方法),用來聯(lián)通讀取流和寫入流,并自動控制兩者的速率
pipe
就像可以把兩個水管串成一個更長的水管一樣,兩個流也可以串起來。一個Readable
流和一個Writable
流串起來后,所有的數(shù)據(jù)自動從Readable
流進入Writable
流,這種操作叫pipe
。
讓我們用pipe()
把一個文件流和另一個文件流串起來,這樣源文件的所有數(shù)據(jù)就自動寫入到目標文件里了,所以,這實際上是一個復制文件的程序:
const fs = require("fs"); const readStream = fs.createReadStream("./1.txt"); const writeStream = fs.createWriteStream("./2.txt"); // 將1.txt內的數(shù)據(jù)復制到2.txt中 // 若開始時沒有2.txt文件,則會自動創(chuàng)建 // 若開始時有2.txt文件,則會使用1.txt的內容替換掉其中的內容 readStream.pipe(writeStream);
?? 注意:
我們使用readStream.pipe(writeStream)
時,數(shù)據(jù)是從左到右,從可讀流到可寫流傳遞,既將readStream
的數(shù)據(jù)傳遞到writeStream
中
小技巧:
pipe
管道可以鏈式調用,這在下一節(jié)我們講到gzip
時會用到
七、補充
判斷路徑是否存在:
fs
中exists
方法能夠判斷某一路徑是否存在,但exists
異步的方法官方已經不建議使用,建議使用的是加Sync
的同步方法:
const fs = require("fs"); // exists方法:判斷路徑是否存在,異步的方法官方已經不建議使用,建議使用的是加Sync的同步方法 console.log(fs.existsSync("./stream流")); // true
結語
本篇文章詳細講解了node.js
的內置模塊fs
的常用方法,并介紹了fs
的同步方法與Promise方法,需要注意的是,在node
開發(fā)中應盡量減少同步的寫法,從而避免因同步阻塞代碼執(zhí)行導致服務器宕機
到此這篇關于Node.js 操作本地文件及深入了解 fs 內置模塊的文章就介紹到這了,更多相關Node.js fs 內置模塊內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
手把手教你更優(yōu)雅的修改node_modules里的代碼
這篇文章主要給大家介紹了關于如何更優(yōu)雅的修改node_modules里的代碼的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2023-02-02pnpm workspace管理monorepo項目使用過程詳解
這篇文章主要為大家介紹了pnpm workspace管理monorepo項目使用過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10pm2發(fā)布node配置文件ecosystem.json詳解
這篇文章主要介紹了pm2發(fā)布node配置文件ecosystem.json詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-05-05