首頁  >  文章  >  web前端  >  淺談Nodejs中的可寫流write與實作方法

淺談Nodejs中的可寫流write與實作方法

青灯夜游
青灯夜游轉載
2021-06-21 10:08:542119瀏覽

本篇文章帶大家了解一下Nodejs中的可寫流write,介紹一下Node可寫流write的實作。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。

淺談Nodejs中的可寫流write與實作方法

【推薦學習:《nodejs 教學》】

可寫流-Writable

fs.createWriteStream呼叫範例

  • 首次讀取的資料會真實寫入目標檔案
  • 其餘次讀取的資料要根據讀取資料是否超出highWaterMark ,是的話存入快取區等待寫入目標檔案中
const fs = require("fs");
const path = require("path");
const bPath = path.join(__dirname, "b.txt");
let ws = fs.createWriteStream(bPath, {
  flags: "w",
  encoding: "utf-8",
  autoClose: true,
  start: 0,
  highWaterMark: 3,
});
ws.on("open", function (fd) {
  console.log("open", fd);
});
ws.on("close", function () {
  console.log("close");
});
 //string 或者buffer,ws.write 还有一个boolea的返回值
ws.write("1");
//flag 表示 当前要写的值是直接是否直接写入文件,不能超出了单次最大写入值highWaterMark
let flag = ws.write("1");
console.log({ flag });//true
flag = ws.write("1");
console.log({ flag });//false
flag = ws.write("1");
console.log({ flag });//false
flag = ws.write("14444444");
console.log({ flag });//false
ws.end(); //write+close,没有调用 end 是不会调用 触发close的,看到这里的小伙伴可以尝试注释end() 看看close的console是否有打印
  • 效果

淺談Nodejs中的可寫流write與實作方法

自訂可寫流initWriteStream

繼承EventEmitter發布訂閱

const EventEmitter = require("events");
const fs = require("fs");
class WriteStream extends EventEmitter {}
module.exports = WriteStream;

鍊錶產生佇列做檔案讀取的快取

#鍊錶&隊列的實作

https://juejin.cn/post/6973847774752145445

// 用链表 生成队列 对 文件缓存区的读取 进行优化
const Queue = require("./queue");

初始化實例預設資料constructor()

 constructor(path, options = {}) {
    super();
    this.path = path;
    this.flags = options.flags || "w";
    this.encoding = options.encoding || "utf8";
    this.mode = options.mode || 0o666; //默认8进制 ,6 6 6  三组分别的权限是 可读可写
    this.autoClose = options.start || 0;
    this.highWaterMark = options.highWaterMark || 16 * 1024; //默认一次读取16个字节的数据
    this.len = 0; //用于维持有多少数据还没有被写入文件中
    //是否根据等待当前读取的最大文数据 排空后再写入
    this.needDrain = false; //
    // 缓存队列 用于存放 非第一次的文件读取 到的数据,因为第一次读取 直接塞入目标文件中
    // 除第一次 的文件读取数据的都存放再缓存中
    // this.cache = [];
    // 队列做缓存
    this.cache = new Queue();
    // 标记是否是第一次写入目标文件的标识
    this.writing = false;
    this.start = options.start || 0;
    this.offset = this.start; //偏移量
    this.open();
  }
  • this.mode 檔案操作權限預設0o666(0o表示8進位)

    • 3個6所佔位置分別對應:檔案所屬使用者對它的權限;檔案所屬使用者群組使用者對它的權限;表示其他使用者對它的權限

    • 權限由:r--可讀取(對應數值4),w--可寫(對應數值2),x--可執行(對應數值1,例如資料夾下有.exe 這樣的標識說明點選可以直接執行)組成

    • 所以預設情況下3組使用者對檔案的操作權限都是可讀可寫

#open()

  • #呼叫fs.open()
  • 回呼emit實例open方法,fs.open的回傳值fd做參數傳入
 open() {
    fs.open(this.path, this.flags, this.mode, (err, fd) => {
      this.fd = fd;
      this.emit("open", fd);
    });
  }

write ()

  • 轉換實例傳入的需要寫入的檔案資料格式為buffer
  • 判斷寫入資料長度是否大於highWaterMark,如果達到預期後,檔案讀取到的資料存放再緩存裡不直接寫入目標檔(這裡要排除是否是第一次讀取檔)
  • #執行實例write 傳入的cb 並呼叫clearBuffer 清空快取
  • #判斷是否是第一次讀取,第一次讀取直接寫入呼叫_write(待實作)
  • 快取佇列尾部offer 目前讀取的資料等待寫入目標檔
 write(chunk, encoding = this.encoding, cb = () => {}) {
    //  将数据全部转换成buffer
    chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);

    this.len += chunk.length;
    // console.log({chunk},this.len )
    let returnValue = this.len < this.highWaterMark;
    //当数据写入后,需要在手动的将this.len--
    this.needDrain = !returnValue; //如果达到预期 后 的文件读取 到数据存放再缓存里 不直接写入目标文件
    //清空缓存 对用户传入的回调 进行二次包装
    let userCb = cb;
    cb = () => {
      userCb();
      //清空buffer
      this.clearBuffer();//马上实现
    };

    //此时需要判断 是否是第一次读取,第一次读取 直接写入调用 _write
    if (!this.writing) {
      // 第一次||缓存队列已清空完毕
      this.writing = true;
      // console.log("first write");
      this._write(chunk, encoding, cb);//马上实现
    } else {
    //缓存队列尾部offer 当前读取到的数据等待写入目标文件
      this.cache.offer({
        chunk,
        encoding,
        cb,
      });
    }
    return returnValue;
  }

clearBuffer()依序清空快取佇列

  • 佇列執行順序,先進先出原則
  • this.cache.poll() 依序拿取頭部資料執行this._write寫入目標檔
  • 快取佇列poll出來的data如果不存在,則表示是第一次寫入的行為||快取佇列已清空。 this.writing = false; 下次的檔案讀取可以直接寫入目標檔案
  • 如果this.needDrain又達到預期,檔案讀取到資料存放再快取裡不直接寫入目標檔案
clearBuffer() {
    //写入成功后 调用 clearBuffer--》写入缓存第一个,第一个完成后,再继续 第二个
    let data = this.cache.poll();
    // console.log(&#39;this.cache&#39;,this.cache)
    if (data) {
      //有值 写入文件
      this._write(data.chunk, data.encoding, data.cb);
    } else {
      this.writing = false;
      if (this.needDrain) {
        // 如果是缓存,触发drain
        this.emit("drain");
      }
    }
  }

_write()

  • fs.open()是異步的,成功讀取後fd會是一個number類型
  • #根據fd的type 決定是否訂閱一次open,並回呼自己(直到fd型別為number)
  • fd型別為number:呼叫fs.write,寫入目前的chunk,
 _write(chunk, encoding, cb) {
    if (typeof this.fd !== "number") {
      return this.once("open", () => this._write(chunk, encoding, cb));
    }
    fs.write(this.fd, chunk, 0, chunk.length, this.offset, (err, written) => {
      this.offset += written; //维护偏移量
      this.len -= written; //把缓存的个数减少
      cb(); //写入成功
      // console.log(this.cache);
    });
  }

測試自訂的Writable

const WriteStream = require("./initWriteStream");

let ws = new WriteStream(bPath, {
  highWaterMark: 3,
});

let i = 0;
function write() {
  //写入0-9个
  let flag = true;
  while (i < 10 && flag) {
    flag = ws.write(i++ + "");
     console.log(flag);
  }
}
ws.on("drain", function () {
  // 只有当我们写入的数据达到预期,并且数据被清空后才会触发drain ⌚️
  console.log("写完了");
  write();
});

write();
  • 10個數字,依序寫入,3次達到最大預期值,然後依序清空了3次快取結果符合預期

淺談Nodejs中的可寫流write與實作方法

  • 目標檔案中查看是否正確寫入了我們預期的數值

淺談Nodejs中的可寫流write與實作方法

更多程式相關知識,請造訪:程式設計影片! !

以上是淺談Nodejs中的可寫流write與實作方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除