ㅤㅤㅤ
ㅤㅤㅤ
ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ(一個有信念者所開發出的力量,大於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