Node.js?操作本地文件及深入了解fs內(nèi)置模塊
前言
node.js作為服務(wù)端應(yīng)用,肯定少不了對本地文件的操作,像創(chuàng)建一個目錄、創(chuàng)建一個文件、讀取文件內(nèi)容等都是我們開發(fā)中經(jīng)常需要用到的功能
這篇文章我們將深入學習node的內(nèi)置模塊:fs文件操作模塊,并使用它來操作本地文件,讓我們開始吧!
一、目錄操作
創(chuàng)建目錄
語法:
fs.mkdir(path[, options], callback)
參數(shù):
path- 文件路徑options配置對象,屬性有:recursive是否以遞歸的方式創(chuàng)建目錄(創(chuàng)建嵌套目錄),默認為falsemode設(shè)置目錄權(quán)限,默認為 0777( Windows 不支持)
callback回調(diào)函數(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 回調(diào)函數(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’withFileTypesfiles數(shù)組是否包含<fs.Dirent>對象,默認值:false
callback回調(diào)函數(shù)err:錯誤信息files:目錄里的內(nèi)容,數(shù)組格式
演示:
// 讀取目錄信息
fs.readdir("./newBlog", (err, data) => {
if (err) {
console.log("err", err);
} else {
// 數(shù)組結(jié)構(gòu):包含目錄下的所有文件名
console.log("data", data);
}
});
刪除目錄
語法:
fs.rmdir(path[, options], callback)
參數(shù):
path- 文件路徑callback回調(diào)函數(shù),參數(shù)如下:err錯誤信息
演示:

注意: 在目錄里面有內(nèi)容時,是無法直接刪除該目錄的,需要提前將目錄下的所有內(nèi)容給刪掉,刪除文件會在下面講到:

二、文件操作
創(chuàng)建文件
使用fs.writeFile方法創(chuàng)建一個文件并寫入內(nèi)容,如果該文件本來就存在,則會替換文件原本的內(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時,如要原本沒有需要創(chuàng)建的這個文件,則writeFile會新建這個文件并向其中寫入指定內(nèi)容,但如果原本有這個文件,則writeFile會將原本的這個文件內(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方法向一個文件內(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表示換行,同時也支持其它的轉(zhuǎn)義符號

讀取文件內(nèi)容
使用fs.readFile方法讀取文件內(nèi)容:
// 讀取文件內(nèi)容
fs.readFile("./blog/one.txt", (err, data) => {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
默認讀取的內(nèi)容是nodejs的Buffer數(shù)組格式,我們可以在獲取數(shù)據(jù)時通過toString將其轉(zhuǎn)化成字符串:

也可以直接在讀取文件時指定讀取的編碼格式:

刪除文件
使用fs.unlink方法刪除文件:
// 刪除文件;
fs.unlink("./blog/one.txt", (err) => {
if (err) {
console.log(err);
} else {
console.log("刪除成功!");
}
});
三、 讀取文件/目錄信息
使用fs.stat可以用來獲取指定路徑的內(nèi)容的詳細信息,包括文件大小、創(chuàng)建時間等:
// 讀取文件/目錄信息
fs.stat("./blog/one.txt", (err, stats) => {
if (err) {
console.log(err);
} else {
console.log("stats", stats);
}
});
回調(diào)函數(shù)中的stats參數(shù)會接收到文件的詳細信息對象,其中各個屬性表示的含義如下:
Stats {
// 包含文件的設(shè)備的數(shù)值型標識
dev: 641331036,
// 描述文件類型和模式的位域
mode: 33206,
// 文件的硬鏈接數(shù)量
nlink: 1,
// 文件擁有者的數(shù)值型用戶標識
uid: 0,
// 擁有文件的群組的數(shù)值型群組標識
gid: 0,
// 如果文件表示設(shè)備,則為數(shù)字設(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()判斷該內(nèi)容是否是一個目錄stats.isFile()判斷該文件是否是一個常規(guī)文件
四、同步方法
上面三節(jié)所講的所有fs的方法都是異步的,如下圖所示:

由于Node環(huán)境執(zhí)行的JavaScript代碼是服務(wù)器端代碼,所以絕大部分需要在服務(wù)器運行期反復(fù)執(zhí)行業(yè)務(wù)邏輯的代碼必須使用異步代碼
否則,同步代碼在執(zhí)行時期,服務(wù)器將停止響應(yīng),因為JavaScript只有一個執(zhí)行線程
Sync同步方法
但node也為我們提供了一些fs的同步方法,我們只需在對應(yīng)的方法名后加上Sync即可使用它的同步方法:
fs.mkdirSync("./blog");注意:fs的同步方法沒有callback(回調(diào)函數(shù))參數(shù),需要獲取的內(nèi)容(如readdir方法回調(diào)函數(shù)參數(shù)中的files參數(shù)數(shù)據(jù))會通過函數(shù)return的形式返回出去
大家可以自己動手試一下:在上面我們遇到過的
fs的方法名加上Sync后綴使其變成同步方法。這里就不一一舉例了
更多fs同步的api可見:nodejs官方文檔

捕捉錯誤
需要注意的是:如果我們使用同步寫法,一定要做好錯誤收集與處理!以防止服務(wù)端因同步方法報錯而導致宕機:

刪除不為空目錄的案例
在目錄操作中,我們了解到在目錄內(nèi)容不為空時,我們是無法直接使用rmdir刪除該目錄的
那么我們就需要先使用unlink將目錄內(nèi)的文件刪除掉,這里我們將使用同步方法實現(xiàn)一個通用函數(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)容為目錄,則進行遞歸
rmdirPlus(`${path}/${item}`);
} else {
// 如果該內(nèi)容不為目錄,直接刪除
fs.unlinkSync(`${path}/${item}`);
}
});
// 刪除目錄
fs.rmdirSync(path);
} catch (error) {
console.log("執(zhí)行錯誤!", error);
}
}
在服務(wù)端,如果沒有必要就盡量不要使用同步代碼!非必要不使用
服務(wù)器啟動時如果需要讀取配置文件,或者結(jié)束時需要寫入到狀態(tài)文件時,可以使用同步代碼,因為這些代碼只在啟動和結(jié)束時執(zhí)行一次,不影響服務(wù)器正常運行時的異步執(zhí)行
五、Promise方法
內(nèi)置模塊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 {
// 讀取目錄內(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)容為目錄,則進行遞歸
await rmdirPlus(`${path}/${item}`);
} else {
// 如果該內(nèi)容不為目錄,直接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ù)調(diào)用不會造成阻塞,它內(nèi)部所有的await阻塞都被封裝在一個Promise對象中異步執(zhí)行
六、大文件操作
前面我們是通過readFile方法來讀取文件內(nèi)容,通過writeFile和appendFile來寫入文件內(nèi)容,這些方法對文件數(shù)據(jù)的操作都是一次性操作,即一次性將數(shù)據(jù)讀出或一次性將數(shù)據(jù)寫入
在文件數(shù)據(jù)內(nèi)容比較大時,這些方法的效率就會變得很慢,那有沒有什么效率高的方式呢?這就需要引入fs模塊的stream流了
stream流介紹
stream是Node.js提供的一個僅在服務(wù)區(qū)端可用的模塊,目的是支持“流”這種數(shù)據(jù)結(jié)構(gòu)
什么是流? 流是一種抽象的數(shù)據(jù)結(jié)構(gòu)
想象水流,當在水管中流動時,就可以從某個地方(例如自來水廠)源源不斷地到達另一個地方(比如你家的洗手池)
我們也可以把數(shù)據(jù)看成是數(shù)據(jù)流,比如你敲鍵盤的時候,就可以把每個字符依次連起來,看成字符流。這個流是從鍵盤輸入到應(yīng)用程序,實際上它還對應(yīng)著一個名字:標準輸入流(stdin)
如果應(yīng)用程序把字符一個一個輸出到顯示器上,這也可以看成是一個流,這個流也有名字:標準輸出流(stdout)。流的特點是數(shù)據(jù)是有序的,而且必須依次讀取,或者依次寫入,不能像Array那樣隨機定位
有些流用來讀取數(shù)據(jù),比如從文件讀取數(shù)據(jù)時,可以打開一個文件流,然后從文件流中不斷地讀取數(shù)據(jù)。有些流用來寫入數(shù)據(jù),比如向文件寫入數(shù)據(jù)時,只需要把數(shù)據(jù)不斷地往文件流中寫進去就可以了
讀取數(shù)據(jù)
在Node.js中,讀取流(Readable流) 也是一個對象,我們只需要響應(yīng)流的事件就可以了:
data事件表示流的數(shù)據(jù)已經(jīng)可以讀取了end事件表示這個流已經(jīng)到末尾了,沒有數(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流) 不斷調(diào)用write()方法,最后以end()結(jié)束:
const fs = require("fs");
// 創(chuàng)建一個可寫的流
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ù)制)
我們需要將一個大文件的內(nèi)容復(fù)制到另一個大文件里,這就需要同時使用讀取流和寫入流,但我們自己使用兩者結(jié)合時,可能會無法控制某一方的速率,導致兩方速率不同步,使得最后復(fù)制過去的數(shù)據(jù)不完整
而node貼心的為我們提供了一個pipe管道(Readable流的pipe()方法),用來聯(lián)通讀取流和寫入流,并自動控制兩者的速率
pipe就像可以把兩個水管串成一個更長的水管一樣,兩個流也可以串起來。一個Readable流和一個Writable流串起來后,所有的數(shù)據(jù)自動從Readable流進入Writable流,這種操作叫pipe。
讓我們用pipe()把一個文件流和另一個文件流串起來,這樣源文件的所有數(shù)據(jù)就自動寫入到目標文件里了,所以,這實際上是一個復(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中
// 若開始時沒有2.txt文件,則會自動創(chuàng)建
// 若開始時有2.txt文件,則會使用1.txt的內(nèi)容替換掉其中的內(nèi)容
readStream.pipe(writeStream);?? 注意:
我們使用readStream.pipe(writeStream)時,數(shù)據(jù)是從左到右,從可讀流到可寫流傳遞,既將readStream的數(shù)據(jù)傳遞到writeStream中
小技巧:
pipe管道可以鏈式調(diào)用,這在下一節(jié)我們講到gzip時會用到
七、補充
判斷路徑是否存在:
fs中exists方法能夠判斷某一路徑是否存在,但exists異步的方法官方已經(jīng)不建議使用,建議使用的是加Sync的同步方法:
const fs = require("fs");
// exists方法:判斷路徑是否存在,異步的方法官方已經(jīng)不建議使用,建議使用的是加Sync的同步方法
console.log(fs.existsSync("./stream流")); // true
結(jié)語
本篇文章詳細講解了node.js的內(nèi)置模塊fs的常用方法,并介紹了fs的同步方法與Promise方法,需要注意的是,在node開發(fā)中應(yīng)盡量減少同步的寫法,從而避免因同步阻塞代碼執(zhí)行導致服務(wù)器宕機
到此這篇關(guān)于Node.js 操作本地文件及深入了解 fs 內(nèi)置模塊的文章就介紹到這了,更多相關(guān)Node.js fs 內(nèi)置模塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Node.js?內(nèi)置模塊fs文件系統(tǒng)操作示例詳解
- 總結(jié)Node.js中9種fs模塊文件操作方法(文件夾遞歸刪除知識)
- 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模塊對系統(tǒng)文件及目錄進行讀寫操作的方法詳解
- 淺談Node.js:fs文件系統(tǒng)模塊
- Node.js?中?fs?模塊的高級用法實例詳解
相關(guān)文章
node.js項目如何創(chuàng)建websocket模塊
這篇文章主要介紹了node.js項目如何創(chuàng)建websocket模塊問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-09-09
如何優(yōu)雅地在Node應(yīng)用中進行錯誤異常處理
這篇文章主要介紹了如何優(yōu)雅地在Node應(yīng)用中進行錯誤處理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11
詳解如何利用Nodejs構(gòu)建多進程應(yīng)用
這篇文章主要為大家介紹了如何利用Nodejs構(gòu)建多進程應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
node.js 和HTML5開發(fā)本地桌面應(yīng)用程序
這篇文章主要介紹了node.js 和HTML5開發(fā)本地桌面應(yīng)用程序的相關(guān)資料,需要的朋友可以參考下2016-12-12

