js 讀取檔案_系統性學習 Node.js(6)- 手寫檔案流
往期回顧
歡迎大家關注我的專欄《Node.js 小冊》,由淺入深學習 Node.js。
Node.js 小冊www.zhihu.com流
流 主要分為 「可讀流」 與 「可寫流」,在 Node 中 stream
模組封裝了流的基本操作。我們今天主要介紹的檔案流也是繼承 stream
模組來實現的。
檔案流 針對檔案操作而實現的流。當我們操作檔案時,由於檔案可能特別大,如果一次性操作檔案的所有內容,效能跟記憶體消耗肯定會很高。所以我們可以像流水一樣,一點一點的讀取並操作檔案,這樣每次消耗的效能跟記憶體會很少,cpu 也會有時間去處理其它的任務。
檔案可讀流
讀取檔案,將檔案內容一點一點的讀入記憶體當中。
使用方式
我們先看一下基本的使用方式。
const
如上程式碼所示,我們通過 fs.createStream()
建立了一個可讀流,用來讀取 w-test.js 檔案。
當 on('data')
時,會自動的讀取檔案資料,每次預設讀取 64kb 的內容,也可以通過 highWaterMark
引數來動態改變每次內容流程的閾值。
檔案讀取完畢後會自動觸發 close
事件。
如下程式碼為 createReadStream
可以配置的引數
const
「注意:」 start 跟 end 都是包含的,即 [start, end]。
其實,fs.crateReadStream
就是返回一個 fs.ReadStream
類的例項,所以上述程式碼就等同於:
const
手寫檔案可讀流
瞭解完使用方式,那我們就應該嘗試從原理上去搞定它,接下來,我們手寫一個可讀流。
初始化
首先,ReadStream
是一個類,從表現上來看這個類可以監聽事件即 on('data')
,所以我們應該讓它繼承自 EventEmitter
,如下程式碼:
class
然後我們初始化引數,並開啟檔案,如下程式碼(程式碼中會對關鍵程式碼作註釋):
class
- 讀取檔案之前,我們要先開啟檔案,即
this.open()
。 on('newListener')
newListener
,例如:當我們on('data')
時,會觸發newListener
事件,並且 type 為 'data'。- 這裡當我們監聽到
data
事件繫結(即on('data')
)時,就開始讀取檔案即this.read()
,this.read()
是我們核心方法。
open
open
方法如下:
open
當開啟檔案後記錄下檔案識別符號,即 this.fd
read
read
方法如下:
read
上述每行程式碼都有註釋,相信也不難理解,這裡有幾個關鍵點要注意一下
- 一定要等檔案開啟後才能開始讀取檔案,但是檔案開啟是一個非同步操作,我們並不知道具體的開啟完畢時間,所以,我們會在檔案開啟後觸發一個
on('open')
事件,read 方法內會等open
事件觸發後再次重新呼叫read()
fs.read()
方法之前有講過,可以從我的專欄裡看一下 手寫 fs 核心方法this.flowing
屬性是用來判斷是否是流動的,會用對應的pasue()
方法與resume()
來控制,下面我們來看一下這兩個方法。
pause
pause
resume
resume
完整程式碼
const
檔案可讀流總結
可以看到,我們用了不到 70 行程式碼就實現了一個可讀流,所以原理其實並沒有想象中那麼難,相信大家也很容易就可以掌握。
這裡大家可能會對 fs.open
,````fs.read,
EventEmitter``` 不太熟悉,可以從前文回顧裡看下我之前的文章,這些都是有講到的。
檔案可寫流
顧名思義了,將內容一點一點寫入到檔案裡去。
使用方式
// 使用方式 1:
ws.write()
寫入檔案。這裡有一個返回值,代表是否已經達到最大快取。當我們同步連續呼叫多次write()
時,並不是每次呼叫都立即寫入檔案,而是同一時間只能執行一次寫入操作,所以剩下的會被寫入到快取中,等上一次寫入完畢後再從快取中依次取出執行。所以,這時就會有一個最大的快取大小,預設為 64kb。而這裡的返回值則代表,是否還可以繼續寫入,也就是:是否達到了最大快取。true 代表可以繼續寫入。ws.on('drain')
,如果呼叫ws.write()
返回 false,則當可以繼續寫入資料到流時會觸發 'drain' 事件。
手寫檔案可寫流
接下來我們手寫 WriteStream
初始化
還是老套路,先定義 WriteStream
類,並繼承 EventEmitter
然後,初始化引數。注意看程式碼註釋
const
open()
open 方法沒啥好說的了,跟 ReadStream 一樣的程式碼。
open
write()
最關鍵的方法,執行寫入操作,先看下程式碼。
每行程式碼都有註釋,注意看~~~
write
每行程式碼都有詳細的註釋,直接看程式碼應該都能理解,這裡我再詳細羅列下具體步驟。
「解讀:」
- 首先初始化要被寫入的內容,只支援 buffer 跟 字串,如果是字串則直接轉為 buffer。
- 計算要被寫入的總長度,即
this.writtenLen += chunk.length
- 判斷是否已經超過 highWaterMark
- 判斷是否需要觸發 drain
- 判斷是否已經有正在被寫入的內容了,如果沒有則呼叫
_write()
直接寫入,如果有則放入快取中。當_write()
寫入完畢後,呼叫clearBuffer()
方法,從this.cache
中取出最先被快取的內容進行寫入操作。clearBuffer 方法如下所示
clearBuffer()
clearBuffer
完整程式碼
const
### 檔案可寫流總結
可寫流要比可讀流要稍微複雜一點,主要是要注意每次只能執行一次寫入操作,剩下的要被快取起來,所以就要有一些列的快取控制方案。只要這塊邏輯搞明白,其實跟可讀流也差不多。
最後
碼字不易,如果覺得不錯,就點贊/關注支援一下吧~~~