nodeJs流的使用及原理

hqq2016發表於2018-09-17

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


複製程式碼


相關文章