NodeJS WriteStream寫入流

ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(一個有信念者所開發出的力量,大於99個只有興趣者。—— ×××)
ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ在這裏插入圖片描述

import { EventEmitter } from 'events';
import * as _ from 'lodash';
import { StringDecoder } from 'string_decoder';
import * as fs from 'fs';

/**
 * @event 爲什麼要使用Stream?
 * @description
 * 缺點:
 * 1. 單獨使用fs.readFile讀取文件,必須讀取完之後才能返回數據,等待時間較長
 * 2. 如果文件過大,則需要佔用大量內存,對內存要求條件變高
 * 優點:
 * 通過流的形式陸續傳入內存進行操作
 * 1. 減少內存開銷壓力
 * 2. 減少文件操作等待時長
 * 3. 優化用戶體驗
 */

/**
 * @event 使用fs模塊創建可讀流
 * @description fs內部已經封裝了Stream,所以可以直接使用而不用自己實現
 */

const readStream = fs.createReadStream('write.json');
let data = '';
readStream.on('data', (value) => {
  data += value;
});
readStream.on('end', () => {
  // console.log(`數據讀取完畢 ${data}`);
});

/**
 * @event 使用fs模塊創建可寫流,並在讀取完成之後使用pipe管道寫入可寫流中
 */
const writeStream = fs.createWriteStream('test.json');
readStream.pipe(writeStream);

/**
 * @event 自定義可寫流
 * @description
 * 繼承 stream 模塊的 Writable 類
 * 重寫Stream模塊的_write方法
 */

class WritableCalss extends EventEmitter {

  public fd: number | null;
  public path: string;
  public flags: string;
  public buffer: any[];
  public pos: number;
  public start: number;
  public length: number;
  public encoding: string;
  public writing: boolean;
  public needDrain: boolean;
  public autoClose: boolean;
  public highWaterMark: number;
  public _decoder: StringDecoder;

  // 調用Writable的構造函數
  constructor(path: string, options?:
    Partial<Record<'flags' | 'encoding', string> & Record<'start' | 'highWaterMark', number>> & { autoClose?: boolean, fd?: number | null }) {
    super();

    // 文件寫入路徑
    this.path = path;

    // 將buffer轉換爲string類型
    this._decoder = new StringDecoder();

    // 設置文件標識位
    this.flags = options.flags || 'w';

    // 字符編碼
    this.encoding = options.encoding || 'utf-8'

    // 文件描述符
    this.fd = options.fd || null;

    // 是否自動關閉
    this.autoClose = options.autoClose || true;

    // 寫入閥值
    this.highWaterMark = options.highWaterMark || 16 * 1024;

    /**
     * 是否正在寫入,默認false
     * 只要緩存區中存在未寫入的buffer,該狀態就爲true
     */
    this.writing = false;

    /**
     * 如果writing爲true,則出發drain事件,將neegDrain更改爲true
     */
    this.needDrain = false;

    /**
     * 緩存區數組,用於存放寫入的數據緩存
     */
    this.buffer = [];

    // 當前緩存的個數,默認爲0
    this.length = 0;

    // 寫入文件的起始位置,起始值默認爲0
    this.start = options.start || 0;

    // 下次寫入文件的位置,根據start變量的變化而變化
    this.pos = this.start;

    // 創建可讀流,需要打開文件
    this.open();
  }

  write(chunk: string | Buffer, encoding: string) {
    if (Buffer.isBuffer(chunk)) {
      // 將buffer轉換爲字符串
      chunk = this._decoder.write(chunk);
    }

    // 維護當前緩存的長度
    this.length += chunk.length;

    // 維護是否觸發drain事件,緩存區長度大於等於閥值則更改drain狀態
    this.needDrain = this.highWaterMark <= this.length;

    // 如果正在寫入
    if (this.writing) {
      this.buffer.push({ chunk, encoding });
    } else {
      // 如果沒有寫入,則更改狀態爲寫入
      this.writing = true;

      // 由於當前狀態爲正在寫入,所以清空緩存區
      this.writeFile(chunk, encoding);

    }

    // 如果高於閥值則爲false,所以需要取反
    return !this.needDrain;
  }

  /**
   * 寫入文件方法
   * @param chunk  數據
   * @param encoding 格式
   * @param callback 回調
   */
  private writeFile(chunk: string | Buffer, encoding: string) {
    const len = fs.writeSync(
      this.fd,
      `${chunk}"\"` as any,
      this.pos,
      chunk.length);
    // 維護下次寫入的位置和緩存區 Buffer 的總字節數
    this.pos += len;
    this.length -= len;
    this.clearBuffer();
  }

  /**
   * 文件打開方法
   */
  private open() {
    const fd = fs.openSync(this.path, this.flags);
    this.fd = fd;
    this.emit('open');
  }

  /**
   * 關閉文件方法
   */
  private detroy() {
    if (_.isNumber(this.fd)) {
      fs.closeSync(this.fd);
      this.emit('close');
      return;
    }
    this.emit('close');
  }

  private clearBuffer() {
    // 先寫入的在數組前面,從前面取出緩存中的 Buffer
    let buf = this.buffer.shift();

    // 如果存在 buf,證明緩存還有 Buffer 需要寫入
    if (buf) {
      // 遞歸 _write 按照編碼將數據寫入文件
      this.writeFile(buf.chunk, buf.encoding);
    } else {
      // 如果沒有 buf,說明緩存內的內容已經完全寫入文件並清空,需要觸發 drain 事件
      this.emit('drain');

      // 更改正在寫入狀態
      this.writing = false;

      // 更改是否需要觸發 drain 事件狀態
      this.needDrain = false;

    }

  }

}

const writable = new WritableCalss('write.txt', { highWaterMark: 3 });
writable.on('drain', function () {
  console.log('寫入成功');
});
writable.write("zhangzw".toString(), 'utf-8');
writable.write("taohx".toString(), 'utf-8');
writable.write("張周旺".toString(), 'utf-8');
writable.write("陶會雪".toString(), 'utf-8');


// 借鑑文章
https://www.overtaking.top/2018/07/04/20180704175217/#%E5%8F%AF%E5%86%99%E6%B5%81%E7%9A%84%E5%AE%9E%E7%8E%B0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章