欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談手寫node可讀流之流動模式

 更新時間:2018年06月01日 09:57:24   作者:tenor  
這篇文章主要介紹了淺談手寫node可讀流之流動模式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

node的可讀流基于事件

可讀流之流動模式,這種流動模式會有一個"開關(guān)",每次當(dāng)"開關(guān)"開啟的時候,流動模式起作用,如果將這個"開關(guān)"設(shè)置成暫停的話,那么,這個可讀流將不會去讀取文件,直到將這個"開關(guān)"重新置為流動。

讀取文件流程

讀取文件內(nèi)容的流程,主要為:

  1. 打開文件,打開文件成功,將觸發(fā)open事件,如果打開失敗,觸發(fā)error事件和close事件,將文件關(guān)閉。
  2. 開始讀取文件中的內(nèi)容,監(jiān)聽data事件,數(shù)據(jù)處于流動狀態(tài),可通過修改開關(guān)的狀態(tài)來暫停讀取。
  3. 每次讀取到的內(nèi)容放入緩存中,并通過data事件將數(shù)據(jù)發(fā)布出去。
  4. 當(dāng)文件中的內(nèi)容讀取完畢之后,將文件關(guān)閉。

這一系列動作都是基于事件來進行操作的,而node中的事件我們都知道是一種發(fā)布訂閱模式來實現(xiàn)的。

下面我們來看一看,node是如何使用可讀流來讀取文件中的內(nèi)容?

node 可讀流參數(shù)

首先我們通過fs模塊來創(chuàng)建一個可讀流,可讀流接受兩個參數(shù):

  1. 第一個參數(shù)是要讀取的文件地址,在這里指明你要讀取哪個文件。
  2. 第二個參數(shù)是可選項,這個參數(shù)是一個對象,用來指定可讀流的一些具體的參數(shù)。

如下幾個參數(shù)我們來一一說明:

  • highWaterMark:設(shè)置高水位線,這個參數(shù)主要用于在讀取文件時,可讀流會將文件中的內(nèi)容讀取到緩存當(dāng)中,而這里我們需要創(chuàng)建一個buffer來緩存這些數(shù)據(jù),所以這個參數(shù)是用來設(shè)置buffer的大小,如果不對這個參數(shù)進行設(shè)置的話,可讀流默認的配置64k。
  • flags:這個參數(shù)主要用于設(shè)置文件的執(zhí)行模式,比如說我們具體的操作適用于讀取文件還是寫入文件等這些操作。如果是寫入文件的話那我們,使用的是w。如果是讀取文件的話那這個操作符就應(yīng)該是r。

下面這張表格就說明了不同的符號代表不同含義:

符號 含義
r 讀文件,文件不存在報錯
r+ 讀取并寫入,文件不存在報錯
rs 同步讀取文件并忽略緩存
w 寫入文件,不存在則創(chuàng)建,存在則清空
wx 排它寫入文件
w+ 讀取并寫入文件,不存在則創(chuàng)建,存在則清空
wx+ 和w+類似,排他方式打開
a 追加寫入
ax 與a類似,排他方式寫入
a+ 讀取并追加寫入,不存在則創(chuàng)建
ax+ 作用與a+類似,但是以排他方式打開文件

  • autoClose:這個參數(shù)主要用于,對文件的關(guān)閉的一些控制。如果文件再打開的過程或者其他操作的過程中出現(xiàn)了錯誤的情況下,需要將文件進行關(guān)閉。那這個參數(shù)是設(shè)置文件是否自動關(guān)閉的功能。
  • encoding:node中用buffer來讀取文件操作的東西二進制數(shù)據(jù)。這些數(shù)據(jù)展現(xiàn)出來的話我們是一堆亂碼,所以需要,要我們對這個數(shù)據(jù)指定一個具體的編碼格式。然后將會對這些數(shù)據(jù)進行編碼轉(zhuǎn)化,這樣轉(zhuǎn)化出來的數(shù)據(jù)就是我們能看懂的數(shù)據(jù)。
  • starts:這個參數(shù)主要用于指定從什么位置開始讀取文件中的內(nèi)容,默認的話是從零開始。
  • ends:這個參數(shù)主要用于指定定具體要讀取文件多長的數(shù)據(jù),這里需要說明一下,這個參數(shù)是包括本身的位置,也就是所謂的包前和包后。

下面我們來看看可讀流具體例子:

let fs = require("fs");
let rs = fs.createReadStream("./a.js", {
  highWaterMark: 3,
  encoding: "utf8",
  autoClose: true,
  start: 0,
  end: 9
});
rs.on("open", () => {console.log("open");});
rs.on("close", () => {console.log("close");});
rs.on("data", data => {
  console.log(data);
  rs.pause();//暫停讀取 此時流動模式為暫停模式
});
setInterval(() => {
  rs.resume();//重新設(shè)置為流動模式,開始讀取數(shù)據(jù)
}, 1000);
rs.on("end", () => { console.log("end"); });
rs.on("error", err => { console.log(err); });

手寫可讀流第一步

上面我們說過,node可讀流是基于node的核心模塊事件來完成的,所以在實現(xiàn)我們自己的可讀流時需要繼承events模塊,代碼如下:

let fs = require('fs');
let EventEmitter = require('events');
class ReadStream extends EventEmitter {

}

繼承了EventEmitter類,我們就可以使用EventEmitter類中的各個方法,并且同樣是采用發(fā)布訂閱的模式了處理事件。

第二步:處理可讀流配置的參數(shù)

上面我們提到,node中創(chuàng)建可讀流時可以對這個流配置具體的參數(shù),比如

let rs = fs.createReadStream("./a.js", {
  highWaterMark: 3,
  encoding: "utf8",
  autoClose: true,
  start: 0,
  end: 9
});

那么對于這些參數(shù),我們自己實現(xiàn)的可讀流類也需要對這些參數(shù)進行處理,那么這些參數(shù)該如何進行處理呢?

constructor(path, options = {}) {
  super();
  this.path = path; //指定要讀取的文件地址
  this.highWaterMark = options.highWaterMark || 64 * 1024;
  this.autoClose = options.autoClose || true; //是否自動關(guān)閉文件
  this.start = options.start || 0; //從文件哪個位置開始讀取
  this.pos = this.start; // pos會隨著讀取的位置改變
  this.end = options.end || null; // null表示沒傳遞
  this.encoding = options.encoding || null;// buffer編碼
  this.flags = options.flags || 'r';

  this.flowing = null; // 模式開關(guān)
  this.buffer = Buffer.alloc(this.highWaterMark);// 根據(jù)設(shè)置創(chuàng)建一個buffer存儲讀出來的數(shù)
  this.open();
}

通常配置的原則是以用戶配置的參數(shù)為準,如果用戶沒有對這個參數(shù)進行設(shè)置的話,就采用默認的配置。

實現(xiàn)可讀流第三步:打開文件

這里原理是使用node模塊fs中的open方法。首先我們來回顧下fs.open()方法的使用。

fs.open(filename,flags,[mode],callback);
//實例
fs.open('./1,txt','r',function(err,fd){});

這里需要說明下,回調(diào)函數(shù)callback中有2個參數(shù):

  1. 第一個是error,node中異步回調(diào)都會返回的一個參數(shù),用來說明具體的錯誤信息
  2. 第二個參數(shù)是fd,是文件描述符,用來標識文件,等價于open函數(shù)的第一個參數(shù)

好了,現(xiàn)在我們來看看我們自己的可讀流的open方法該如何實現(xiàn)吧:

open() {
  fs.open(this.path, this.flags, (err, fd) => { 
    //fd標識的就是當(dāng)前this.path這個文件,從3開始(number類型)
    if (err) {
      if (this.autoClose) { // 如果需要自動關(guān)閉則去關(guān)閉文件
        this.destroy(); // 銷毀(關(guān)閉文件,觸發(fā)關(guān)閉事件)
      }
      this.emit('error', err); // 如果有錯誤觸發(fā)error事件
      return;
    }
    this.fd = fd; // 保存文件描述符
    this.emit('open', this.fd); // 觸發(fā)文件的打開的方法
  });
}

從代碼上我們可以看出:

fs.open函數(shù)是異步函數(shù),也就是說callback是異步執(zhí)行的,在成功打開文件的情況下,fd這個屬性也是異步獲取到的,這點需要注意。

另外重要的一點是,如果在打開文件發(fā)生錯誤時,則表明打開文件失敗,那么此時就需要將文件關(guān)閉。

實現(xiàn)可讀流第四步:讀取文件內(nèi)容

上面我們詳細說過,可讀流自身定義了一個"開關(guān)",當(dāng)我們要讀取文件中的內(nèi)容的時候,我們需要將這個"開關(guān)"打開,那么node可讀流本身是如何來打開這個"開關(guān)"的呢?

監(jiān)聽data事件

node可讀流通過監(jiān)聽data事件來實現(xiàn)這個"開關(guān)"的開啟:

rs.on("data", data => {
  console.log(data);
});

當(dāng)用戶監(jiān)聽data事件的時候,"開關(guān)"開啟,不停的從文件中讀取內(nèi)容。那么node是怎么監(jiān)聽data事件的呢?
答案就是 事件模塊的newListener

這是因為node可讀流是基于事件的,而事件中,服務(wù)器就可以通過newListener事件監(jiān)聽到從用戶這邊過來的所有事件,每個事件都有對應(yīng)的類型,當(dāng)用戶監(jiān)聽的是data事件的時候,我們就可以獲取到,然后就可以去讀取文件中的內(nèi)容了,那我們自己的可讀流該如何實現(xiàn)呢?

// 監(jiān)聽newListener事件,看是否監(jiān)聽了data事件,如果監(jiān)聽了data事件的話,就開始啟動流動模式,讀取文件中的內(nèi)容
this.on("newListener", type => {
  if (type === "data") {
    // 開啟流動模式,開始讀取文件中的內(nèi)容
    this.flowing = true;
    this.read();
  }
});

好了,知道了這個"開關(guān)"是如何打開的,那么這個時候就到了真正讀取文件中內(nèi)容的關(guān)鍵時候了,先上代碼先:

read() {
  // 第一次讀取文件的話,有可能文件是還沒有打開的,此時this.fd可能還沒有值
  if (typeof this.fd !== "number") {
    // 如果此時文件還是沒有打開的話,就觸發(fā)一次open事件,這樣文件就真的打開了,然后再讀取
    return this.once("open", () => this.read());
  }
  // 具體每次讀取多少個字符,需要進行計算,因為最后一次讀取倒的可能比highWaterMark小
  let howMuchRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark;
  fs.read(this.fd, this.buffer, 0, howMuchRead, this.pos, (err, byteRead) => {
    // this.pos 是每次讀取文件讀取的位置,是一個偏移量,每次讀取會發(fā)生變化
    this.pos += byteRead;
    // 將讀取到的內(nèi)容轉(zhuǎn)換成字符串串,然后通過data事件,將內(nèi)容發(fā)布出去
    let srr = this.encoding ? this.buffer.slice(0, byteRead).toString(this.encoding) : this.buffer.slice(0, byteRead);
    // 將內(nèi)容通過data事件發(fā)布出去
    this.emit("data", srr);
    // 當(dāng)讀取到到內(nèi)容長度和設(shè)置的highWaterMark一致的話,并且還是流動模式的話,就繼續(xù)讀取
    if ((byteRead === this.highWaterMark) && this.flowing) {
      return this.read();
    }
    // 沒有更多的內(nèi)容了,此時表示文件中的內(nèi)容已經(jīng)讀取完畢
    if (byteRead < this.highWaterMark) {
      // 讀取完成,發(fā)布end方法,并關(guān)閉文件
      this.emit("end");
      this.destory();
    }
  });
}

這里我們特別要注意的是:

  1. 文件是否已經(jīng)打開,是否獲取到fd,如果沒有打開的話,則再次觸發(fā)open方法
  2. 分批次讀取文件內(nèi)容,每次讀取的內(nèi)容是變化的,所以位置和偏移量是要動態(tài)計算的
  3. 控制讀取停止的條件。

實現(xiàn)可讀流第五步:關(guān)閉文件

好了,到現(xiàn)在,基礎(chǔ)的讀取工作已經(jīng)完成,那么就需要將文件關(guān)閉了,上面的open和read方法里面都調(diào)用了一個方法:destory,沒錯,這個就是關(guān)閉文件的方法,好了,那么我們來看看這個方法該如何實現(xiàn)吧

destory() {
  if (typeof this.fd !== "number") {
    // 發(fā)布close事件
    return this.emit("close");
  }
  // 將文件關(guān)閉,發(fā)布close事件
  fs.close(this.fd, () => {
    this.emit("close");
  });
}

當(dāng)然這塊的原理就是調(diào)用fs模塊的close方法啦。

實現(xiàn)可讀流第六步:暫停和恢復(fù)

既然都說了,node可讀流有一個神奇的"開關(guān)",就像大壩的閥門一樣,可以控制水的流動,同樣也可以控制水的暫停啦。當(dāng)然在node可讀流中的暫停是停止對文件的讀取,恢復(fù)就是將開關(guān)打開,繼續(xù)讀取文件內(nèi)容,那么這兩個分別對應(yīng)的方法就是pause()和resume()方法。

那么我們自己的可讀流類里面該如何實現(xiàn)這兩個方法的功能呢?非常簡單:

我們在定義類的私有屬性的時候,定義了這樣一個屬性flowing,當(dāng)它的值為true時表示開關(guān)打開,反之關(guān)閉。

pause() {
  this.flowing = false;// 將流動模式設(shè)置成暫停模式,不會讀取文件
}
resume() {
  this.flowing = true;//將模式設(shè)置成流動模式,可以讀取文件
  this.read();// 重新開始讀取文件
}

好了,關(guān)于node可讀流的實現(xiàn)我們就寫到這里,快快敲起代碼,動手實現(xiàn)一個你自己的可讀流吧!

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論