NodeJS Stream

什么是 Stream

在 Unix 系统中流就是一个很常见也很重要的概念,从术语上讲流是对输入输出设备的抽象。

ls | grep *.js

类似这样的代码我们在写脚本的时候经常可以遇到,使用  | 连接两条命令,把前一个命令的结果作为后一个命令的参数传入,这样数据像是水流在管道中传递,每个命令类似一个处理器,对数据做一些加工,因此 | 被称为 “ 管道符号”。

NodeJS 中 Stream 的几种类型

从程序角度而言流是 有方向的数据,按照流动方向可以分为三种流

  1. 设备流向程序:readable
  2. 程序流向设备:writable
  3. 双向:duplex、transform
NodeJS 关于流的操作被封装到了  Stream 模块,这个模块也被多个核心模块所引用。按照 Unix 的哲学:一切皆文件,在 NodeJS 中对文件的处理多数使用流来完成

  1. 普通文件
  2. 设备文件(stdin、stdout)
  3. 网络文件(http、net)
下面来几个小例子

1、从另外一个文件读取,然后再写入当前的文件中

var fs = require('fs');

//readFileSync 声名一个可读可写的流
var source = fs.readFileSync('../Buffer/img.jpg'); // 读出文件流的数据 

//再写进当前的文件中
fs.writeFileSync('stream_copy_img.jpg' , source);
这种方法太爆力,直接读直接写,所以在读取文件流 和 写入文件流 的时候是有监听事件的

2、读取的监听事件

var fs = require('fs');

//声名一个只读的流,读取 du.js 文件
var readStream = fs.createReadStream('du.js');

readStream
	.on('data', function(chunk){  //数据正在传递的时候会触发该事件
		console.log('正在传递');
		console.log(Buffer.isBuffer(chunk));  //看传递的是不是 Buffer
		console.log(chunk.toString('utf8'));  //查看传递的内容
	})
	.on('readable', function(){
		//这个文件是可读的
		console.log('这个文件是可读的')
	})
	.on('end', function(){
		//数据传递完成后的事件 ,完成后目标就不再可写了
		console.log('传递完成');
	})
	.on('close', function(){
		//流关闭后会触发事件
		console.log('已关闭');
	})
	.on('error', function(err){
		//错误   比如说文件不存在什么的
		console.log('错误:' + err)
	})
执行后走过了 data、 readable、 end、 close 的监听,在data 里还打印了读取的数据

3、而在传递过程中是可 暂定 ,然后一会再接着开始

var n = 0;
readStream
	.on('data', function(chunk){  //数据正在传递的时候会触发该事件
		n ++ 
		console.log('正在传递');
		readStream.pause(); // 暂停
		console.log('暂停');
		setTimeout(function(){
			console.log('接着开始');
			readStream.resume();
		},3000)
	})
	.on('end', function(){
		//数据传递完成后的事件 ,完成后目标就不再可写了
		console.log('传递完成');
		console.log(n);
	})
在 data 里面监听到有数据传递的时候用 readStream.pause()   暂停,然后 3秒种后用 readStream.resume();  接着开始,这里是走了一次。

4、写入的监听

data 事件里传递的数据如果是大文件并不是次就能传递完的,会分次传递,然后等到传递完,如果在 data 里直接加写会的话是读取完了才会写,大文件如果全部读出来再写,有N多次请求的时候可能会把内存都爆满了,所以最好是边读边写 边读边写 边读边写

下面是把一个文件 da_img.jpg 读取到再写一个同样的文件 new_da_img.jpg 在当前文件中

var fs = require('fs');

//声名一个只读的流,读一个大文件,大文件如果全部读出来再写,N多次的时候可能会把内存都爆满了,
//所以最好是 边读边写 边读边写 边读边写
var readStream = fs.createReadStream('da_img.jpg');

//声名一个写的流,写入当前文件中
var writeStream = fs.createWriteStream('new_da_img.jpg')

//监听读出来的时候 
//
readStream
	.on('data', function(chunk){  //数据正在传递的时候会触发该事件

		//检测到有东西读出来了
		if(writeStream.write(chunk) === false ){//如果读出来的东西没写完
			console.log('没写完,等会……')
			//先暂停一下
			readStream.pause();
		};
	})
	.on('end', function(){
		writeStream.end();//做完就关闭
		console.log('传递完成');
	})


// 写的流 writeStream 有个监听数据是否写完的方法 
// 
writeStream.on('drain', function(){
	console.log('没写完,继续')
	//数据写完后再接着开始读
	readStream.resume();
})
当 data 里监听到有数据进来的时候用 writeStream.write(chunk) 开始写入  === false 就是第二次有数据传递进来的时候我第一次还没写完,然后就用 readStream.pause()先暂停一会,等我写完先,然后再监听写入的事件 writeStream 的 ‘ drain’方法,写完后再继续开始读取


pipe

其实上面的小例子,让文件  边读边写 系统自带有一个  pipe 方法,可以完成以上的代码,两行代码就可以搞定。哈哈哈……

var fs = require('fs');
//读一个大文件 da_img.jpg ,然后把写放方法放到 pipe 中,pipe直接帮我们完成了边读边写的功能
 fs.createReadStream('da_img.jpg').pipe(fs.createWriteStream('1-pipe.jpg'));

在读取文件和写入文件的时候,系统的方法往往不能满足项目的一些需求

读写的原理是:

可读流是负责读取外部的数据,并把数据缓存到内部的 buffer 数组

可写流负责消费数据,从可读流里获取到数据,然后对得到的数据块进行处理,怎么处理就取决于内部的 _write 方法,我们可以重写  _write 方法可以看 下面的小例子

var Readable = require('stream').Readable //拿到 可读流的构造函数
var Writable = require('stream').Writable //拿到 可写流的构造函数

var readStream = new Readable()   //new 一个可读流实例
var writStream = new Writable()   //new 一个可写流实例

readStream.push('I ')    // 对可读流 readStream 添加一些数据
readStream.push('Love ')
readStream.push('Imooc\n')
readStream.push(null)

//重写 可写流的 _write方法
writStream._write = function(chunk, encode, cb){
	console.log(chunk.toString())
	cb()
}

//最后用 pipe 把 可读流 和 可写流 连接起来
readStream.pipe(writStream)

下面来个更详细的示例

示例的内容是自己定制 可读流  可写流   转换流的方法,然后通过 util 让自己定制的 可读流、可写流、转换流 继承原型里的 可读流、可写流、转换流 方法并改掉重写一些里面的方法,最后用 pipe 把它们连接起来,  让它更适合自己用

var stream = require('stream')  //拿到 stream
var util = require('util')  //拿到 工具类

// 自己定制的可读流
	function ReadStream(){
		// 改变它的上下文,让它可以调用 可读类的方法
		stream.Readable.call(this)
	}

	// 通过 util.inherits 来声名 自己定制的可读流 继承可读流里面原型
	util.inherits(ReadStream, stream.Readable)

	//为可读流添加原型链上的 _read 方法
	//可读流就是往里面添加数据 
	ReadStream.prototype._read = function(){
		readStream.push('I ')    // 对可读流 readStream 添加一些数据
		readStream.push('Love ')
		readStream.push('Imooc\n')
		readStream.push(null
	}


// 自己定制的可写流
	function WritStream(){
		// 改变它的上下文,让它可以调用 可读类的方法
		stream.Writable.call(this)
		this._cached = new Buffer('')
	}
	// 通过 util.inherits 来声名 自己定制的可写流 继承可写流里面原型
	util.inherits(WritStream, stream.Writable)

	//为可写流添加原型链上的 _write 方法 (即重写掉 _write 方法)
	WritStream.prototype._write = function(chunk, encode, cb){
		console.log(chunk.toString())
		cb()
	}


// 自己定制的转换流
	function TransformStream(){
		// 改变它的上下文,让它可以调用 转换流的方法
		stream.Transform.call(this)
	}
	// 通过 util.inherits 来声名 自己定制的转换流 继承转换流里面原型
	util.inherits(TransformStream, stream.Transform)

	//为转换流添加原型链上的 transform 方法 (即重写掉 _transform 方法)
	TransformStream.prototype._transform = function(chunk, encode, cb){
		console.log(chunk.toString())
		cb()
	}

	//重点是 _flush
	TransformStream.prototype._flush = function(chunk, encode, cb){
		//为读到的数据加一些额外的内容
		this.push('Oh yes')
		cb()
	}

//生成数据 

//1、
var rs = new ReadStream()  // new 可读流
var ws = new WritStream()  // new 可写流
var ts = new TransformStream()  // new 转化流

//2、把读到的数据 pipe 给转换流,让它对内容进行额外的定制和处理,然后再pipe给可写流打印
rs.pipe(ts).pipe(ws)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值