Pro
一個createWriteStream
的簡單實現,以求能增加對可寫流的理解與應用。
引數配置
/**
* createWriteStream
* @param1 path
* @param2 options
*/
let fs = require('fs');
let ws = fs.createWriteStream('./1.txt',{
flags:'w'//檔案的開啟模式
,mode:0o666//檔案的許可權設定
,encoding:'utf8'//寫入檔案的字元的編碼
,highWaterMark:3//最高水位線
,start:0 //寫入檔案的起始索引位置
,autoClose:true//是否自動關閉文件
})
複製程式碼
createWriteStream類的例項化
- 例項化一個
createWriteStream
類- 將
path
,options
掛載在createWriteStream
的例項上,除此之外再在例項上掛載以下屬性self.fd=null
:檔案開啟後返回的檔案描述符self.pos=self.start
:用於表示檔案真正寫入時的指標位置self.Buffer=[]
:用來表示檔案的緩衝區self.len=null
:用來表示緩衝區此時的大小self.isWriting=false
:用來表示是否正在真正寫入檔案
- 呼叫
open
方法,開啟檔案(發射open事件)
- 將
例項write方法的執行流程
wirte
方法接收三個引數,chunk
要寫入的內容,encoding
要進行的,cb
回撥函式。write
執行流程:- 判斷傳入的
chunk
是否為buffer,如果不是,則轉換成buffer,用於轉化編碼依據傳入的encoding
引數。 - 更新
Buffer
緩衝區的len
長度,讓len加上該次chunk的長度 - 判斷
len
是否已經超過highWaterMark
,將值存入flag
- 判斷是否處於
isWriting
狀態:- 是,則先加
chunk
寫入例項物件下的Buffer緩衝區
。 - 否,更新
isWriting
,接將引數傳遞給例項下的_write
方法寫入檔案
- 是,則先加
- 返回
flag
- 判斷傳入的
例項_write方法的執行流程
此方法用於真正寫入檔案
- 檢視例項的
fd
屬性是否存在(檔案是否開啟成功)- 成功,呼叫
fs
模組的write
方法正式寫入資料- 更新例項物件下的
len
以及pos
屬性 - 呼叫
clearBuffer
方法將緩衝區的內容寫入 - 呼叫write方法傳入的回撥函式
cb
- 更新例項物件下的
- 失敗,訂閱一個
open事件
(open事件將會在open方法中被髮射),在訂閱中的回撥方法中再次以相同的引數呼叫_write方法
- 成功,呼叫
例項clearBuffer方法
- 從緩衝區中取出一個資料
- 如果資料存在,呼叫
_write
方法 - 如果資料不存在,將
isWriting
更改為false,發射drain
事件
- 如果資料存在,呼叫
實現原始碼以及測試檔案
let fs = require('fs');
let EventEmitter = require('events');
class WriteStream extends EventEmitter {
constructor(path, options) {
super();
let self = this;
Object.assign(self, options); //還需設定預設值
self.path = path;
self.isWriting = false;
self.Buffer = []; //原始碼中為連結串列實現的緩衝區
self.len = null;
self.pos = self.start; //初始化寫入位置
self.fd = null;
self.open();
}
open() {
let self = this;
fs.open(self.path, self.flags, self.mode, (err, fd) => {
self.fd = fd;
if (err) return self.destroy(err);
self.emit('open');
});
}
destroy(err) {
fs.close(this.fd, () => {
this.emit('error', err);
});
}
write(chunk, encoding, cb) {
let self = this
, ret = null;
encoding = encoding?encoding:self.encoding; //優先使用write傳入的編碼方式
chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding);
self.len += chunk.length;
ret = self.highWaterMark > self.len; //判斷當前最新的緩衝區是否已達到最高水位線
if (self.isWriting) { //說明正在呼叫底層方法真正寫入檔案,先寫入Buffer
self.Buffer.push({
chunk
, cb
});
} else {
self.isWriting = true;
self._write(chunk, cb, () => self.clearBuffer());
}
return ret;
}
_write(chunk, cb, clear) {
let self = this;
if (!self.fd) return self.once('open', () => {
self._write(chunk, cb, clear)
});
fs.write(self.fd, chunk, 0, chunk.length, self.pos, (err, bytesWritten) => {
if (err) {
if (self.autoClose) {
self.destroy();
self.emit('error', err);
}
}
self.len -= bytesWritten;
self.pos += bytesWritten;
cb && cb();
clear && clear();
});
}
clearBuffer() {
let self = this
, data = null;
data = self.Buffer.shift();
if (data) {
self._write(data.chunk, data.cb, () => self.clearBuffer());
} else { //此時說明緩衝區已無資料
self.isWriting = false;
self.emit('drain');
}
}
}
module.exports = WriteStream;
複製程式碼
測試檔案:
let WriteStream = require('./practice');
let ws = new WriteStream('./1.txt',{
flags:'w'
,mode:0o666
,start:0
,encoding:'utf8'
,autoClose:true //當流寫完之後自動關閉檔案
,highWaterMark:3
});
let n = 9;
ws.on('error',(err)=>{
console.log(err)
})
function write(){
let flag = true;
while(flag&&n>0){
flag = ws.write(n+"",'utf8',()=>{
console.log('ok');
});
n--;
console.log('flag=',flag)
}
ws.once('drain',()=>{
console.log('drain');
write();
})
}
// ws.on('drain',()=>{
// console.log('drain');
// write();
// })
write();
複製程式碼
參考資料: nodejs.org/dist/latest…