1. 程式人生 > 其它 >js 讀取檔案_系統性學習 Node.js(6)- 手寫檔案流

js 讀取檔案_系統性學習 Node.js(6)- 手寫檔案流

技術標籤:js 讀取檔案js讀取檔案

往期回顧

歡迎大家關注我的專欄《Node.js 小冊》,由淺入深學習 Node.js。

Node.js 小冊​www.zhihu.com f0acc24bf7286969d897d0a7f402d6c9.png

主要分為 「可讀流」「可寫流」,在 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')
    是 EventEmitter 的一個事件,每當我們繫結新的事件時都會觸發 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.readEventEmitter``` 不太熟悉,可以從前文回顧裡看下我之前的文章,這些都是有講到的。

檔案可寫流

顧名思義了,將內容一點一點寫入到檔案裡去。

使用方式

// 使用方式 1:
  • ws.write() 寫入檔案。這裡有一個返回值,代表是否已經達到最大快取。當我們同步連續呼叫多次 write()時,並不是每次呼叫都立即寫入檔案,而是同一時間只能執行一次寫入操作,所以剩下的會被寫入到快取中,等上一次寫入完畢後再從快取中依次取出執行。所以,這時就會有一個最大的快取大小,預設為 64kb。而這裡的返回值則代表,是否還可以繼續寫入,也就是:是否達到了最大快取。true 代表可以繼續寫入。
  • ws.on('drain'),如果呼叫ws.write()返回 false,則當可以繼續寫入資料到流時會觸發 'drain' 事件。

手寫檔案可寫流

接下來我們手寫 WriteStream

初始化

還是老套路,先定義 WriteStream 類,並繼承 EventEmitter

然後,初始化引數。注意看程式碼註釋

const 

open()

open 方法沒啥好說的了,跟 ReadStream 一樣的程式碼。

open

write()

最關鍵的方法,執行寫入操作,先看下程式碼。

每行程式碼都有註釋,注意看~~~

write

每行程式碼都有詳細的註釋,直接看程式碼應該都能理解,這裡我再詳細羅列下具體步驟。

「解讀:」

  1. 首先初始化要被寫入的內容,只支援 buffer 跟 字串,如果是字串則直接轉為 buffer。
  2. 計算要被寫入的總長度,即 this.writtenLen += chunk.length
  3. 判斷是否已經超過 highWaterMark
  4. 判斷是否需要觸發 drain
  5. 判斷是否已經有正在被寫入的內容了,如果沒有則呼叫 _write() 直接寫入,如果有則放入快取中。當 _write() 寫入完畢後,呼叫 clearBuffer() 方法,從 this.cache 中取出最先被快取的內容進行寫入操作。clearBuffer 方法如下所示

clearBuffer()

clearBuffer

完整程式碼

const 

### 檔案可寫流總結
可寫流要比可讀流要稍微複雜一點,主要是要注意每次只能執行一次寫入操作,剩下的要被快取起來,所以就要有一些列的快取控制方案。只要這塊邏輯搞明白,其實跟可讀流也差不多。

最後

碼字不易,如果覺得不錯,就點贊/關注支援一下吧~~~