node的流,就是對檔案的處理,包括可讀流,可寫流,雙工流,壓縮流
可讀流主要是對檔案進行讀取,或者是對輸入進行讀取
可寫流主要是用來對檔案進行寫入,或者是進行輸出
可讀流寫法
// readFile writeFile// 會將檔案讀取到記憶體中 佔用記憶體
// fs.read fs.write 檔案模組中 單獨提供了檔案的讀流和寫流
// 有起點和終點// 每次讀幾個
// 讀流中讀取出內容 匯入到寫流中// 流有多種方式
// 檔案的可讀流let fs = require('fs');
let ReadStream = require('./ReadStream3')
let rs = new ReadStream('1.txt', {
encoding: null, // 編碼
flags: 'r+', // 標識要讀取檔案
highWaterMark: 3, // 最高水位線,每次讀多少個
autoClose: true, // 讀取後關閉
start: 0, // 開始讀取的位置
end: 3 // 結束的位置
});
// 事件機制,需要自己去監聽一些資料
// 預設不流動 非流動模式,需要監聽data事件,內部會按照highwaterMark 讀取資料把資料在內部發射出來
let arr = [];
rs.on('open', function () {
console.log(' 檔案開啟了')
});
rs.on('data', function (data) {
console.log(data); arr.push(data);
});
rs.on('end', function () { // 只有目標檔案讀取完畢後才觸發
console.log('結束了');
console.log(Buffer.concat(arr).toString());
});// rs.pause()
rs.on('error', function (err) {
console.log('出錯了')
})
rs.on('close', function () {
console.log('close')
});
複製程式碼
fs.createReadStream方法
引數(path, options = { enconding:編碼, flags: 處理的型別, highWaterMark: 最高水位線,每次讀取的最大位元組, autoClose檔案讀取完成後是否自動關閉, start:開始讀取的位置, end:結束讀取的位置 })
返回一個rs例項,使用釋出訂閱模式
rs.on('open', callback) rs.on('data', callback) rs.on('end', callback)
open是檔案剛開啟觸發的事件
data是每次讀取的內容(會根據每次讀取的highWaterMark分多次讀取)
end是檔案讀取結束觸發的事件
下面我們來解析程式碼
let EventEmitter = require('events')
let fs = require('fs')
// 需要繼承EventEmitter
// 使用釋出訂閱模式處理回撥問題
class ReadStream extends EventEmitter {
constructor(path, options = {}) {
super()
// 讀取檔案的路徑
this.path = path
// 編碼型別
this.encoding = options.encoding || null
// 處理檔案的模式預設讀取
this.flags = options.flags || 'r'
// 最大水位線預設64kb
this.highWaterMark = options.highWaterMark || 64 * 1024
// 是否自動關閉,結束讀取或者error後是否會自動關閉fd
this.autoClose = options.autoClose || true
// 開始讀取的位置
this.start = options.start || 0
// 結束讀取的位置
this.end = options.end || null
// 標記檔案讀取到的位置
this.pos = this.start
// 檔案流動模式,用來處理讀取的開始和暫停
this.flowing = false
// 初始化開啟檔案
this.open()
this.on('newListener', type => {
// 如果是data事件才開始讀取
if (type === 'data') {
this.flowing = true
this.read()
}
})
}
destory() {
if (!this.fd) {
this.emit('close')
} else {
// 如果有fd說明檔案開啟過,需要關閉fd
fs.close(this.fd, () => {
this.emit('close')
})
}
}
open() {
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
this.emit('error', err)
this.autoClose && this.destory()
} else {
this.fd = fd
// 開啟觸發open事件
this.emit('open', fd)
}
})
}
pause() {
// 關閉流動模式,暫停
this.flowing = false
}
resume() {
// 恢復流動,開啟
this.flowing = true
this.read()
}
// 檔案讀取方法
read() {
if (!this.fd) return this.once('open', () => this.read())
if (!this.flowing) return false
// 建立buffer判斷
// howMuchRead是取最後到當前位置和highWaterMark最小值,防止溢位
let howMuchRead = this.end ? Math.min(this.end - this.pos + 1, this.highWaterMark) : this.highWaterMark
let buffer = Buffer.alloc(howMuchRead)
fs.read(this.fd, buffer, 0, howMuchRead, this.pos, (err, bytesRead) => {
if (err) {
this.emit('error', err)
this.autoClose && this.destory()
} else {
if (bytesRead > 0) {
this.pos += bytesRead
this.emit('data', buffer.slice(0, bytesRead))
this.read()
} else {
this.emit('end')
this.autoClose && this.destory()
}
}
})
}
}
module.exports = ReadStream
複製程式碼
下面我們來看看可寫流用法
let fs = require('fs')
let ws = fs.createWriteStream('2.txt', {
flags: 'w', //行為
encoding: 'utf8', //編碼
mode: 0o666, //許可權
autoClose: true, //是否自動關閉
start: 0, //開始位置
highWaterMark: 3 //最大水位線
})
let flag = ws.write('hello', 'utf8', () => console.log('ok'))
console.log(flag)
//flag用來標記流是否低於最大水位線,此時應該是false
flag = ws.write('2')
ws.on('drain', () => {
console.log('我drain了')
})複製程式碼
ws.write()方法會將內容寫入目標檔案,寫入的過程是非同步的,會有一個快取區,如果多次write會將第一次需要寫入的內容進行寫入操作,後面的會存入快取區,等待第一次寫入結束再從快取區拿出來繼續寫入
如果寫入的值超過了highWaterMark,會觸發darin事件
那麼我們來看看如何實現寫入流的吧
let EventEmitter = require('events')
let fs = require('fs')
class WriteStream extends EventEmitter {
constructor(path, options = { }) {
super()
this.path = path
this.flags = options.flags || 'w'
this.encoding = options.encoding || null
this.mode = options.mode || 0o666
this.autoClose = options.autoClose || true
this.start = options.start || 0
this.highWaterMark = options.highWaterMark || 16 * 1024
// 偏移量
this.pos = this.start
// 是否正在寫入
this.writing = false
// 快取區
this.cache = []
// 記錄長度
this.len = 0
// 寫入完畢是否觸發閥值
this.needDrain = false
this.open()
}
// 銷燬方法
destory() {
if (!this.fd) {
this.emit('close')
} else {
fs.fsync(this.fd, () => {
fs.close(this.fd, () => {
this.emit('close')
})
})
}
}
open() {
fs.open(this.path, this.flags, (err, fd) => {
if (err) {
this.emit('error', err)
this.autoClose && this.destory()
} else {
this.fd = fd
this.emit('open', fd)
}
})
}
write(chunk, encoding, callback) {
chunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
this.len += chunk.length
if (this.len >= this.highWaterMark) {
this.needDrain = true
}
if (this.writing) {
this.cache.push({chunk, encoding, callback})
} else {
this._write(chunk, encoding, () => {callback(); this.clearBuffer()})
}
return this.highWaterMark > this.len
}
clearBuffer() {
let obj = this.cache.shift()
if (obj) {
this._write(obj.chunk, obj.encoding, () => {obj.callback(); this.clearBuffer()})
} else {
this.len = 0
this.writing = false
if (this.needDrain) {
this.needDrain = false
this.emit('drain')
}
}
}
_write(chunk, encoding, callback) {
if (!this.fd) return this.emit('open', () => this._write(chunk, encoding, callback))
fs.write(this.fd, chunk, 0, chunk.length, this.pos, (err, bytesWritting) => {
if (err) {
this.writing = false
this.emit('error', err)
this.autoClose && this.destory()
} else {
this.pos += bytesWritting
this.emit('data', chunk)
callback()
}
})
}
}
module.exports = WriteStream
複製程式碼