http://segmentfault.com/a/1190000003479884

1. 认识Stream

  • Stream的概念最早来源于Unix系统,其可以将一个大型系统拆分成一些小的组件,然后将这些小的组件可以很好地运行。
  • TCP/IP协议中的TCP协议也用到了Stream的思想,进而可以进行流量控制、差错控制
  • 在unix中通过 |来表示流;node中通过pipe方法
  • Stream可以认为数据就像管道一样,多次不断地被传递下去,而不是一次性全部传递给下游

2. node中的stream

node stream中可以看到第一段的描述:

A stream is an abstract interface implemented by various objects in Node. For
example a request to an HTTP server is a stream, as is stdout. Streams are
readable, writable, or both. All streams are instances of EventEmitter

对上面一段话进行解析,可以得到如下几点:

  • Stream是Node中一个非常重要的概念,被大量对象实现,尤其是Node中的I/O操作
  • Stream是一个抽像的接口,一般不会直接使用,需要实现内部的某些抽象方法(例如_read、_write、_transform)
  • Stream是EventEmitter的子类,实际上Stream的数据传递内部依然是通过事件(data)来实现的
  • Stream分为四种:readable、writeable、Duplex、transform

3.Readable Stream 与 Writeable Stream

3.1 二者的关系

Readable Stream是提供数据的Stream,外部来源的数据均会存储到内部的buffer数组内缓存起来。

writeable Stream是消费数据的Stream,从readable stream中获取数据,然后对得到的chunk块数据进行处理,至于如何处理,就依赖于具体实现(也就是_write的实现)。

首先看看Readdable Streamwriteable stream二者之间的流动关系:

3.2 pipe的流程解析

stream内部是如何从readable stream流到writeable stream里面呢?有两种方法:

a) pipe 连接两个stream

先看一个简单地demo

var Read = require('stream').Readable;
var Write = require('stream').Writable;
var r = new Read();
var w = new Write(); r.push('hello ');
r.push('world!');
r.push(null) w._write = function (chunk, ev, cb) {
console.log(chunk.toString());
cb();
} r.pipe(w);

pipe是一种最简单直接的方法连接两个stream,内部实现了数据传递的整个过程,在开发的时候不需要关注内部数据的流动:

Readable.prototype.pipe = function (dest, pipeOpts) {
var src = this;
...
src.on('data', ondata); function ondata(chunk) {
var ret = dest.write(chunk);
if (false === ret) {
debug('false write response, pause',
src._readableState.awaitDrain);
src._readableState.awaitDrain++;
src.pause();
}
}
...
}

b) 事件data + 事件drain联合实现

var Read = require('stream').Readable;
var Write = require('stream').Writable;
var r = new Read();
var w = new Write(); r.push('hello ');
r.push('world!');
r.push(null) w._write = function (chunk, ev, cb) {
console.log(chunk.toString());
cb();
} r.on('data', function (chunk) {
if (!w.write(chunk)) {
r.pause();
}
}) w.on('drain', function () {
r.resume();
}) // hello
// world!

4 Readable Stream的模式

4.1 内部模式的实现

Readable Stream 存在两种模式(flowing mode 与 paused mode),这两种模式决定了chunk数据流动的方式---自动流动还是手工流动。那如何触发这两种模式呢:

  • flowing mode: 注册事件data、调用resume方法、调用pipe方法
  • paused mode: 调用pause方法(没有pipe方法)、移除data事件 && unpipe所有pipe

让我们再深入一些,看看里面具体是如何实现的:

// data事件触发flowing mode
Readable.prototype.on = function(ev, fn) {
...
if (ev === 'data' && false !== this._readableState.flowing) {
this.resume();
}
...
} // resume触发flowing mode
Readable.prototype.resume = function() {
var state = this._readableState;
if (!state.flowing) {
debug('resume');
state.flowing = true;
resume(this, state);
}
return this;
} // pipe方法触发flowing模式
Readable.prototype.resume = function() {
if (!state.flowing) {
this.resume()
}
}

结论

  • 两种方式取决于一个flowing字段:true --> flowing mode;false --> paused mode
  • 三种方式最后均是通过resume方法,将state.flowing = true

4.2 两种模式的操作

a. paused mode

在paused mode下,需要手动地读取数据,并且可以直接指定读取数据的长度:

var Read = require('stream').Readable;
var r = new Read(); r.push('hello');
r.push('world');
r.push(null); console.log('输出结果为: ', r.read(1).toString())
// 输出结果为: 'h'

还可以通过监听事件readable,触发时手工读取chunk数据:

var Read = require('stream').Readable;
var r = new Read(); r.push('hello');
r.push('world');
r.push(null); r.on('readable', function () {
var chunk = r.read();
console.log('get data by readable event: ', chunk.toString())
}); // get data by readable event: hello world!

需要注意的是,一旦注册了readable事件,必须手工读取read数据,否则数据就会流失,看看内部实现:

function emitReadable_(stream) {
debug('emit readable');
stream.emit('readable');
flow(stream);
} function flow(stream) {
var state = stream._readableState;
debug('flow', state.flowing);
if (state.flowing) {
do {
var chunk = stream.read();
} while (null !== chunk && state.flowing);
}
} Readable.prototype.read = function (n) {
...
var res = fromList(n, state); if (!util.isNull(ret)) {
this.emit('data', ret);
}
...
}

flow方法直接read数据,将得到的数据通过事件data交付出去,然而此处没有注册data事件监控,因此,得到的chunk数据并没有交付给任何对象,这样数据就白白流失了,所以在触发emit('readable')时,需要提前read数据。

b. flowing mode

通过注册data、pipe、resume可以自动获取所需要的数据,看看内部实现:

// 事件data方式
var Read = require('stream').Readable; var r = new Read(); r.push('hello ');
r.push('world!');
r.push(null) r.on('data', function (chunk) {
console.log('chunk :', chunk.toString())
})
// chunk : hello
// chunk : world!
// 通过pipe方式
var r = new Read(); r.push('hello ');
r.push('world!');
r.push(null) r.pipe(process.stdout)
// hello world!

c. 两种mode的总结

5. transform stream的实现

用过browserify的人都知道,browserify是一种基于stream的模块打包工具,里面存在browserify.prototype.transform(tr)方法,其中的tr就要求是transform stream,且browserify内部通过through2构建了很多tranform stream。也可以说browserify是建立在transform stream的基础上。那么具备readable、writeablestream的transform stream内部是如何工作的呢?

6. 自定义stream

自定义stream很简单,只要实现相应的内部待实现方法就可以了,具体来说:

  • readable stream: 实现_read方法来解决数据的获取问题
  • writeable stream: 实现_write方法来解决数据的去向问题
  • tranform stream: 实现_tranform方法来解决数据存放在buffer前的转换工作
// 自定义readable stream的实现
var Stream = require('stream');
var Read = Stream.Readable;
var util = require('util'); util.inherits(MyReadStream, Read); function MyReadStream(data, opt) {
Read.call(this, opt);
this.data = data || [];
}
MyReadStream.prototype._read = function () {
var _this = this;
this.data.forEach(function (d) {
_this.push(d);
})
this.push(null);
} var data = ['aa', 'bb', 'cc'];
var r = new MyReadStream(data); r.on('data', function (chunk) {
console.log(chunk.toString());
})

7. 参考资料

Stream探究的更多相关文章

  1. 探究 Redis 4 的 stream 类型

    redis 2 10 月初,Redis 搞了个大新闻.别紧张,是个好消息:Redis 引入了名为 stream 的新数据类型和对应的命令,大概会在年底正式发布到 4.x 版本中.像引入新数据类型这样的 ...

  2. Java NIO的探究

    1.Java NIO与阻塞IO的区别 阻塞IO通信模型(在上一篇<J2SE网络编程之 TCP与UDP>博客中有所介绍) 我们知道阻塞I/O在调用InputStream.read()方法时是 ...

  3. Java 8新特性探究(八)精简的JRE详解

    http://www.importnew.com/14926.html     首页 所有文章 资讯 Web 架构 基础技术 书籍 教程 Java小组 工具资源 - 导航条 - 首页 所有文章 资讯 ...

  4. 深入理解Stream流水线

    前面我们已经学会如何使用Stream API,用起来真的很爽,但简洁的方法下面似乎隐藏着无尽的秘密,如此强大的API是如何实现的呢?Pipeline是怎么执行的,每次方法调用都会导致一次迭代吗?自动并 ...

  5. 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...

  6. Nodejs cluster模块深入探究

    由表及里 HTTP服务器用于响应来自客户端的请求,当客户端请求数逐渐增大时服务端的处理机制有多种,如tomcat的多线程.nginx的事件循环等.而对于node而言,由于其也采用事件循环和异步I/O机 ...

  7. SpringCloud学习之DiscoveryClient探究

    当我们使用@DiscoveryClient注解的时候,会不会有如下疑问:它为什么会进行注册服务的操作,它不是应该用作服务发现的吗?下面我们就来深入的来探究一下其源码. 一.Springframewor ...

  8. 深入理解Java Stream流水线

    前面我们已经学会如何使用Stream API,用起来真的很爽,但简洁的方法下面似乎隐藏着无尽的秘密,如此强大的API是如何实现的呢?Pipeline是怎么执行的,每次方法调用都会导致一次迭代吗?自动并 ...

  9. java8 Stream的实现原理 (从零开始实现一个stream流)

    1.Stream 流的介绍 1.1 java8 stream介绍 java8新增了stream流的特性,能够让用户以函数式的方式.更为简单的操纵集合等数据结构,并实现了用户无感知的并行计算. 1.2  ...

随机推荐

  1. IOS开发之—— Core Foundation对象与OC对象相对转换的问题

    对ARC盲目依赖的同学: 1过度使用block后,无法解决循环引用问题 2遇到底层Core Foundation对象,需要自己手工管理它们的引用计数时,显得一筹莫展 first:对于底层Core Fo ...

  2. GDB代码调试与使用

    GDB代码调试与使用 Linux下GDB调试代码 源代码 编译生成执行文件 gcc -g test.c -o test 使用GDB调试 启动GDB:gdb test 从第一行列出源代码:list 直接 ...

  3. 【MPI学习7】MPI并行程序设计模式:MPI的进程组和通信域

    基于都志辉老师MPI编程书中的第15章内容. 通信域是MPI的重要概念:MPI的通信在通信域的控制和维护下进行 → 所有MPI通信任务都直接或间接用到通信域这一参数 → 对通信域的重组和划分可以方便实 ...

  4. Windows Phone8 中如何引用 SQLite 数据库2

    本博文编写环境 VS2013 + WP8 SDK 上篇介绍完了SQLite在wp中的部署(具体请参阅 Windows Phone8 中如何引用 SQLite 数据库),下面来看如何使用 SQLite ...

  5. PHP+微信分享自定义小图标

    微信分享以后的小图标如下图: <script>document.addEventListener('WeixinJSBridgeReady', function onBridgeReady ...

  6. Pivot的SelectionChanged事件绑定到VM的Command

    我要实现的是页面加载时,只获取SelectedIndex=0的数据,然后根据Pivot的SelectionChanged动态获取其他项的数据,我用的是MVVM的Command的方式,不想用后台注册事件 ...

  7. 教你用netstat-实践案例

    netstat命令的功能是显示网络连接.路由表和网络接口信息,可以让用户得知目前都有哪些网络连接正在运作. 该命令的一般格式为: netstat [选项] 命令中各选项的含义如下: -a 显示所有so ...

  8. webstorm调试Node的时候配置

    点击Edit Configurations的这个的配置:(不能点击是因为目前你选中的不是项目)

  9. [c#基础]值类型和引用类型的Equals,==的区别

    引言 最近一个朋友正在找工作,他说在笔试题中遇到Equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外.为了证实自己的说法,也研究了一 ...

  10. web前端开发教程系列-1 - 前端开发编辑器介绍

    目录: 前言 一. Webstorm 1. 优点 2. 缺点 3. 教程 4. 插件 5. 技巧 二. SublimeText 1. 优点 2. 缺点 3. 教程 4. 插件 5. 技巧 前言 由于很 ...