浅析node中的Stream流

Stream - 流

stream是一个比较抽象的模型,类似于没有水的水流,stream.write可以让水流中有水(数据),每次一点一点写的小数据叫做chunk(块),产生数据的一段叫做source(源头),得到数据的一段叫做sink(水池)。

Stream 对象的原型链

s = fs.createReadStream(path)
  • 代码中对象的层级为
  • 自身属性(由fs.ReadStream构造)
  • 原型:stream.Readable.prototype
  • 二级原型:stream.Stream.prototype
  • 三级原型:events.EventEmitter.prototype
  • 四级原型:Object.prototype
  • Stream 对象都继承了EventEmitter

Stream 支持的事件和方法

Readable Stream Writable Stream
事件 data,end,error,close,readable drain,finish,error,close,pipe,unpipe
方法 pipe(),unpipe()
warp()
destory()
read()
unshift()
resume(),pause()
isPaused()
setEncoding()
wriet()
destory()
end()
cork(), uncork()
setDefaultEncoding()
  • drain事件:某部分写完,stream干涸不再拥堵(stream中空闲下来了),通知可以继续进行写入
  • finish事件:整个全部写完,不再写入了
    stream流方法事件详细介绍:http://nodejs.cn/api-v16/stream.html

Readable 和 Writable

Readable Stream

  • 静止态 paused 和流动态 flowing
  • 默认处于paused 态
  • 添加 data 事件监听,他就会变成 flowing 态
  • 删除 data 事件监听,他就会变成 paused 态
  • pause() 可以将它变成 paused
  • resume() 可以将它变成 flowing

Writable Stream

  • drain 流干了事件
  • 调用stream.write(chunk)时,可能会得到 false
  • 造成 false 的原因是写的太快,数据积压了
  • 这时候不能再 write,要监听 drain
  • 当 drain 事件触发时,才能继续write
  • finish事件
  • 调用 stream.end() 之后
  • 且缓冲区数据都已经传给底层系统之后
  • 触发 finish 事件

Duplex 和 Transform 的区别

image.png
  • Duplex 读和写是两条,边读边写,不会交汇,内容不同,在没有其他设置的情况下,自己无法读到写的内容
  • Transform 读写在同一条,中间存在转换器,可将写入的流转换为读取流需要的格式,从而进行读取

fs.readFile() 和 fs.createReadStream()

// fs.readFile() 读的速度快,但由于是一次性大量传输,因此会占用大量内存
server.on('request', (request, response) => {
    fs.readFile('./big_file.txt', (err, data) => {
        if (err) throw err
        response.end(data)
        console.log('done')
    })
})


// fs.createReadStream() 读的速度略慢,但由于是一点一点传输,因此占用内存较小,基本控制在30M以内
server.on('request', (request, response) => {
    const stream = fs.createReadStream('./big_file.txt')
    stream.pipe(response)
    console.log('done')
})

管道 pipe

释义

两个stream流可以用一个管道相连,stream1 的末尾接上 stream2 的开端,只要 stream1 有数据,就会流到 stream2,可以降低内存占用

代码示例

// 两个流可以连起来,stream1 一有数据,就通过管道 pipe 传送给 stream2,使用流可以降低内存占用
stream1.pipe(stream2)

a.pipe(b).pipe(c)
// 等价于
a.pipe
b.pipe

管道可以通过事件实现

// stream1一有数据chunk就塞给stream2
stream1.on('data', (chunk)=>{
    stream2.write(chunk)
})

// stream1听了,就停掉stream2
stream1.on('end', ()=>{
    stream2.end()
})

创建自己的流

Writable Stream
实现功能:输入什么就输出什么

const {Writable} = require("stream")


// 创建自己的可写的流
const outStream = new Writable({
    write(chunk, encoding, callback) {
        console.log(chunk.toString());
        callback();
    }
});

// process.stdin 标注输入
process.stdin.pipe(outStream)

Readable Stream
实现功能:根据控制,读取并输出范围内的值

const {Readable} = require("stream")


// 创建自己的可读的流
const inStream = new Readable({
    read(size) {
        this.push(String.fromCharCode(this.currentCharCode++));

// 控制只能读取 currentCharCode 大于90的值
// Z
        if (this.currentCharCode > 90) {
            this.push(null)
        }
    }
})

// 通过currentCharCode控制读取范围
// A,读取从65-90,即从A-Z
inStream.currentCharCode = 65

inStream.pipe(process.stdout)

Duplex Stream
实现功能:兼具Writable Stream和Readable Stream功能

const {Duplex} = require("stream");

// 读写分两条流,因此需要定义write()和read()分别负责读写
const inoutStream = new Duplex({
    write(chunk, encoding, callback) {
        console.log(chunk.toString());
        callback();
    }, read(size) {
        this.push(String.fromCharCode(this.currentCharCode++));
        if (this.currentCharCode > 90) {
            this.push(null);
        }
    }
});

inoutStream.currentCharCode = 65;
process.stdin.pipe(inoutStream).pipe(process.stdout);

Transform Stream
实现功能:将输入的小写字母转换为大写字母并输出

const {Transform} = require("stream");

// transform 中间有转换器,因此可以在同一条流内处理读写
const upperCaseTr = new Transform({
    transform(chunk, encoding, callback) {
        // 将输入的小写字母转化为大写字母
        this.push(chunk.toString().toUpperCase());
        callback();
    }
});

// 程序输入流中的值流入upperCaseTr操作,转换为大写字母
// 将大写字母 push 而后又流入process.stdout输出
process.stdin.pipe(upperCaseTr).pipe(process.stdout);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 接触过 Node.js 的开发人员可能知道,流(Stream)这个概念比较难理解,也不太好处理。 用 Domini...
    1024译站阅读 1,500评论 0 1
  • 第一个stream的例子: 创建流,多次往里面填充内容,关闭流 最终得到一个100MB的文件 stream-流 s...
    西域战神阅读 525评论 0 0
  • stream 流是一个抽象接口,在 Node 里被不同的对象实现。例如 request to an HTTP se...
    明明三省阅读 3,404评论 1 10
  • node.js中的流是一种数据传输手段,流是有顺序的。流不关心整体流程,只管取出数据,获取数据后的操作 流有四种基...
    柳贤_e8c5阅读 299评论 0 0
  • 简介 主要对stream这个概念做一个形象的描述和理解,同时介绍一下比较常用的API。主要参考了Node.js的官...
    cooody阅读 1,202评论 0 0