掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
Node.js 提供了 File System 的 api,可以讀寫文件、目錄、修改權(quán)限、創(chuàng)建軟鏈等。

創(chuàng)新互聯(lián)建站是一家朝氣蓬勃的網(wǎng)站建設(shè)公司。公司專注于為企業(yè)提供信息化建設(shè)解決方案。從事網(wǎng)站開發(fā),網(wǎng)站制作,網(wǎng)站設(shè)計(jì),網(wǎng)站模板,微信公眾號開發(fā),軟件開發(fā),微信小程序開發(fā),十年建站對成都砂巖浮雕等多個行業(yè),擁有豐富的網(wǎng)站設(shè)計(jì)經(jīng)驗(yàn)。
可能大家 api 用的比較熟練,但對于這些 api 的原理不一定理解。要想真正理解 File System,還得從根上來看。
下面我們從 0 到 1 設(shè)計(jì)一個文件系統(tǒng)試試。
什么是文件呢?
這樣一份比較完整的資料就是文件。
但是計(jì)算機(jī)的持久化存儲是在硬盤,主要是磁盤和 SSD 固定硬盤:
計(jì)算機(jī)里的文件是一個邏輯概念,并不是物理上存在的一個個實(shí)體。
那么如果給了這樣一個硬盤,讓我們自己造一個文件系統(tǒng)出來,實(shí)現(xiàn)文件的功能。怎么做呢?
簡單想了下:挨著放。
然后還要記錄下索引,啥文件在啥位置:
這樣是可以,但是有個問題,萬一文件 B 被刪除了,那對應(yīng)的空間就要釋放:
然后又來了一個文件 D,發(fā)現(xiàn)放不進(jìn)去啊,這地方太小了。
A 和 B 中間這塊空間就是碎片,碎片會把磁盤可用空間割裂成不連續(xù)的很多小塊。
怎么辦呢?如何更好的利用磁盤空間?
分塊!把文件分成一小塊一小塊的,比如 1k 為一個塊,可以不用連續(xù)存,把所有塊記錄在索引中就行了。
索引咋記錄的看不清,放大點(diǎn)來看:
每個文件的索引中都記錄了把數(shù)據(jù)存在了哪幾個塊,這樣一塊一塊的讀出來就行了。
而且文件刪除了,那些塊還可以繼續(xù)用,不會有很大的碎片。
妙啊!
難怪文件系統(tǒng)第一步就是分塊,內(nèi)存管理第一步是分頁,這樣才能高效利用空間啊。
這種索引節(jié)點(diǎn),可以叫做 index node,簡稱 inode 就好了。
而且,除了文件名和存放的塊以外,還可以記錄其他信息,比如創(chuàng)建時間、修改時間、文件權(quán)限、所屬用戶等等。
到這里就可以對文件下個物理層面上的定義了: 文件就是 inode 記錄的信息和它所索引的一系列數(shù)據(jù)塊。
那我寫文件利用了一個塊,刪除文件釋放了一個塊,怎么知道呢?得單獨(dú)記錄下來塊的狀態(tài)。
數(shù)據(jù)塊只有空閑和被占用兩個狀態(tài),一個二進(jìn)制位就行了。通過一段二進(jìn)制數(shù)把所有塊的空閑狀態(tài)記錄下來。一個位代表一個值,這叫做位圖。在這里就是塊位圖。
硬盤中大部分是數(shù)據(jù)塊,開頭的一段來放 inode 所在的塊和數(shù)據(jù)塊的塊位圖。
inode 也是存在塊里的,比如我們規(guī)定只能用 5 個塊放 inode,那 inode 總量是有限的,也就是是說文件系統(tǒng)可以創(chuàng)建的文件是有上限的。
我們也要記錄下所有 inode 的空閑狀態(tài),也就是 inode 位圖。
簡單理一下我們設(shè)計(jì)的文件系統(tǒng):
為了更好的利用硬盤空間,我們對數(shù)據(jù)進(jìn)行了分塊,每個文件用到了哪些塊記錄在 inode 里。inode 還記錄了文件的創(chuàng)建時間、修改時間、權(quán)限等信息。
通過塊位圖來記錄數(shù)據(jù)塊空閑狀態(tài),通過 inode 位圖來記錄 inode 的空閑狀態(tài)。
但我如果想知道硬盤中有幾個塊、用了幾個,有幾個 inode、用了幾個,怎么辦呢?
簡單,遍歷一遍塊位圖和 inode 位圖,就知道個數(shù)了。
但每次這么算太慢了,這就像我們設(shè)計(jì)數(shù)據(jù)庫的時候,一個論壇下面有多少個帖子,這個數(shù)據(jù)不會是每次用 sql 查詢的,而是在帖子增刪的時候動態(tài)維護(hù)一個字段在數(shù)據(jù)庫表中,直接查詢即可。
那我們也設(shè)計(jì)一個塊用來存這種統(tǒng)計(jì)信息,也就是:
這個塊是較高層的統(tǒng)計(jì)信息,我們可以叫它超級塊。
現(xiàn)在把我們設(shè)計(jì)好的文件系統(tǒng)交給用戶,就可以通過它來高效利用硬盤了。
發(fā)布版本:神光文件系統(tǒng) V1.0。
但現(xiàn)在我們的文件系統(tǒng)好像還不是很好用,只能創(chuàng)建文件,那如果我創(chuàng)建了 1000 個文件呢?
查詢起來慢,也容易命名沖突。
怎么辦呢?
命名空間!目錄!就像文件夾的思想一樣。
那怎么實(shí)現(xiàn)呢?
每個 inode 是一個文件,那把 inode 組織成樹不就行了。
比如我們有兩個文件 B 和 C:
我們創(chuàng)建一個目錄 A:
在 inode 里面添加一個 isDirectory 的屬性,如果是目錄,那么就讀取數(shù)據(jù)塊的內(nèi)容,找到其中的 inode 節(jié)點(diǎn)編號,就知道該目錄下的文件了。
這就是目錄的實(shí)現(xiàn)原理:通過 inode 的 isDirectory 屬性區(qū)分是文件還是目錄,如果是目錄就讀取數(shù)據(jù)塊中的 inode 信息來查找子文件,如果是文件,則直接讀取數(shù)據(jù)塊作為文件內(nèi)容。
從一個 inode 到另一個 inode 的查找順序叫做路徑(path)。
比如這樣一個文件的 inode 查找順序:
那文件路徑就是 /A/D/dongdong.jpg
這就是文件路徑的本質(zhì):文件路徑就是 inode 查找順序
現(xiàn)在我們支持目錄的嵌套了,可以把文件、目錄組織成樹形結(jié)構(gòu)方便管理。
發(fā)布版本:神光文件系統(tǒng) v2.0。
現(xiàn)在一個 inode 只有一條路徑過來,因?yàn)槭菢渎铮侨绻蚁雰蓷l路徑都可以找到同一個 inode 呢?
比如 /A/D/dongdong.jpg 可以訪問到該文件:
我想通過 /A/B/dongdong.jpg 也能訪問,怎么辦呢?
直接指過去不就行了。
這樣確實(shí)可以有兩條路徑找到同一個文件,這個額外的鏈接我們起名叫做硬鏈接。
但是因?yàn)橐粋€節(jié)點(diǎn)有兩個父節(jié)點(diǎn),就不再是樹了,變成了圖。所以,文件樹這個概念嚴(yán)格意義上來說還是存在問題的,可能是個文件圖。
但是我如果想給 dongdong.jpg 換個名字,叫 dongdong2.jpg 呢?
現(xiàn)在都是同一個 inode 節(jié)點(diǎn),改了就都改了。但我只想改 /A/B 的 path 的文件名,別的不改。
那就再創(chuàng)建個 inode 節(jié)點(diǎn),用來改名,然后這個 inode 節(jié)點(diǎn)指向 dongdong.jpg。
這種不直接指過去,多了一個 inode 來改名,之后再指過去,這種我們叫做軟鏈接。
為什么叫硬呢?因?yàn)楦牟涣耍苯又赶蛲粋€ inode。
為什么叫軟呢?因?yàn)榭梢愿?,多了一?inode 用來改名。
所以我們分別起名硬軟鏈接。
硬鏈接和軟鏈接都是用于多條路徑可以查找到同一個文件的,但是硬鏈接不能單獨(dú)改名,軟連接可以。
monorepo 的實(shí)現(xiàn)就是基于軟連接的,可以指向同一個目錄 inode,而且還可以起個別名。
實(shí)現(xiàn)了軟硬鏈接,可以發(fā)新版本了。
發(fā)布版本:神光文件系統(tǒng) v3.0。
復(fù)盤一下我們設(shè)計(jì)的文件系統(tǒng):
v1.0:
通過數(shù)據(jù)塊來存儲文件內(nèi)容,提高硬盤利用效率
通過 inode 記錄所用的數(shù)據(jù)塊和文件的創(chuàng)建時間、權(quán)限等信息
通過塊位圖記錄數(shù)據(jù)塊的空閑狀態(tài)
通過 inode 位圖記錄 inode 空閑狀態(tài)。
通過超級塊記錄 inode 和數(shù)據(jù)塊的統(tǒng)計(jì)信息。
這個版本實(shí)現(xiàn)了文件的存取,但是不支持目錄。
v2.0:
通過 inode 中添加一個屬性來記錄是文件還是目錄
目錄的數(shù)據(jù)塊中存放具體文件列表的 inode 信息,讀取目錄的時候可以讀取出文件列表。
按照目錄 inode、文件 inode 的一層層的查找順序叫做文件路徑。
這個版本實(shí)現(xiàn)了目錄和路徑的功能。
v3.0:
通過多個目錄 inode 包含同一個 inode 的方式,來實(shí)現(xiàn)多條路徑查找同一文件的功能,叫做硬鏈接。
目錄先創(chuàng)建一個 inode 節(jié)點(diǎn)用于改名,然后該 inode 節(jié)點(diǎn)指向目標(biāo) inode 節(jié)點(diǎn),這叫做軟連接。
這個版本實(shí)現(xiàn)了多條路徑查找統(tǒng)一文件的軟硬鏈接功能。
真實(shí)的文件系統(tǒng)也是類似的實(shí)現(xiàn),目前有很多文件系統(tǒng),比如 ext2、FAT 等,原理和我們設(shè)計(jì)的文件系統(tǒng)差不多。
文件系統(tǒng)設(shè)計(jì)完了,回到最開始的目標(biāo),我們是想深入理解 Node.js 的 File System 的 api。下面就來看一下。
Node.js 通過 V8 注入了 fs 的 api 給 js 用,底層是通過 c++ 調(diào)用操作系統(tǒng)的文件系統(tǒng)功能,也就是我們上面設(shè)計(jì)的那種文件系統(tǒng)。
我們調(diào)用的 fs 的 api 最終就是調(diào)用了操作系統(tǒng)的文件系統(tǒng)功能。
自己設(shè)計(jì)了一個文件系統(tǒng)之后,我們再來看下 fs 的 api,是不是理解更深了:
具體 api 還有很多,但都是用來操作我們上面設(shè)計(jì)的那個文件系統(tǒng)的。
從根上理解了文件系統(tǒng),用這些 api 也會得心應(yīng)手。
為了真正理解 Node.js 的 fs 模塊,我們一起設(shè)計(jì)了一個文件系統(tǒng):
我們得出一些重要結(jié)論:
文件本質(zhì)上就是 inode + 數(shù)據(jù)塊。
路徑本質(zhì)上就是查找目標(biāo) inode 的路徑。
硬鏈接本質(zhì)上就是多個目錄 inode 包含同一個 inode。
軟連接本質(zhì)上就是多創(chuàng)建了一個 inode 用于改名,對應(yīng)數(shù)據(jù)塊中指向目標(biāo) inode。
Node.js 的 fs api 是通過 c++ 注入 v8 的對操作系統(tǒng)能力的調(diào)用,理解了文件系統(tǒng),再學(xué)那些 api 就很輕松了。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流