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