stream

stream :

几乎所有的Node程序,都会使用Streams,一个简单的例子:

var http = require('http');
//req是一个读取流,res是一个写入流
var server = http.createServer(function (req, res) {  
 var body = '';  
 req.setEncoding('utf8');//设定req的编码为utf8,如果不设定,默认为Buffer对象。
 //读取流会触发data事件。
 req.on('data', function (chunk) {
   body += chunk;
 })

 // 数据读取完的时候会触发end事件
 req.on('end', function () {
   try {
     var data = JSON.parse(body);
   } catch (er) {
     // uh oh!  bad json!
     res.statusCode = 400;
     return res.end('error: ' + er.message);
   }

   // 响应
   res.write(typeof data);
   res.end();
 })})

server.listen(1337);
...

// $ curl localhost:1337 -d '{}'
// object
// $ curl localhost:1337 -d '"foo"'
// string
// $ curl localhost:1337 -d 'not json'
// error: Unexpected token o

stream是一种分部读取源数据的抽象

i/o执行控制有三种情况:串行,完全并行,并行。

流是控制诸如tcp/udp网络数据、文件数据、子进程数据、用户输入数据的一种方法。为了控制这些数据,node提供了多种i/o控制方法:


同步方法异步方法

一次性读入内存
Fully buffered

readFileSync()


readFile()
多次读入内存(流)
Partially buffered (streaming)
readSync()
read(), createReadStream()

     上表的几种读取数据方法的区别在于是否同步读取、所分配的内存大小。一般我们就用readFile,createReadStream.

fs.createReadStream就是readable stream的具体实现,或者说“子类”。

  • 一次性读入内存的方法:

[100 Mb file]
-> 1. [分配100Mb的内存]
-> 2. [读取数据到内存,然后返回100Mb的内存]

     这种方式在数据不大的情况下还能使用,但是在数据很大的时候,比如1Gb的文件,那么简直是内存杀手。

  • 多次读入内存(流)-或者叫部分读入内存

[100 Mb file]
-> 1. [分配一小块内存,len为x]
-> 2. [读取x字节的数据到内存,返回这部分内存]
-> 3. [重复1,2步骤直到文件读取完成]

     多次读入内存允许我们指定使用的内存大小,也允许指定读取文件那一部分,还有很多其他的控制方法。
当然,我们也许不需要那么底层的控制。streams就应运而生,例如createReadStream就产生一个ReadStream对象.
它是多次读入内存(Partially buffered)的抽象,它简化了控制方法。
只要设定一次使用内存的大小,streams就会每次读取该大小的数据,在每次读取到数据时他会触发一个事件(他实现了EventEmitters)。
node中的streams有的是读取流(Readable streams),有的是写入流(Writable streams),有的兼而有之。


读取流,Readable streams

Files               fs.createReadStream(path, [options])

从文件读取,返回一个ReadStream对象

HTTP (Server) http.ServerRequest

http.ServerRequest是ReadStream对象

HTTP (Client)    http.ClientResponse


TCP net.Socket

Socket既是ReadStream,也是WriteStream

Child process child.stdout


Child process child.stderr


Process process.stdin



  • Readable streams会触发下面的内置事件。

Event:'readable'当有数据能够被读取时触发,没用过


Event: ‘data’当Buffer类的数据,或者字符串的数据被接受到的时候触发。默认是buffer,但是设置了setEncoding的时候,那就是字符串了。



Event: ‘end’数据接收完毕时
Event: 'close'
传入文件被关闭时触发,不是所有类型的streams都触发这个事件。。?
Event: ‘error发生错误时




  • 使用stream.on(eventname, callback)处理数据:

var fs = require('fs');
var file = fs.createReadStream('./test.txt');

file.on('error', function(err) {
 console.log('Error '+err);
 throw err;});

file.on('data', function(data) {
 console.log('Data '+data);});

file.on('end', function(){
 console.log('Finished reading all of the data');});

  • Readable streams还有如下方法:

pause()Pauses the incoming 'data' events.
resume()Resumes the incoming 'data' events after a pause().
destroy()Closes the underlying file descriptor. Stream will not emit any more events.
readable.read([size]):只有在非流动(被动)模式才有效。读取当前的数据。
readable.setEncoding(encoding):设置读取流的编码
readable.pipe(destination, [options]):
  • destination -接受并写入数据的写入流

  • options 其中有end项,设置是否在读取流结束的时候,同时关闭写入流,默认 = true

pipe方法将读取流和写入流像两个水管对接一样连在一起,读取流的流数据全部输出到写入流,不会造成阻塞。

var readable = getReadableStreamSomehow();

var writable = fs.createWriteStream('file.txt');
// All the data from readable goes into 'file.txt'
readable.pipe(writable);

pipe方法返回写入流的流数据,就像他是一个读取流从某个i/o正在读取数据一样,所以pipe可以写成链式。
var r = fs.createReadStream('file.txt');
var z = zlib.createGzip();
var w = fs.createWriteStream('file.txt.gz');
r.pipe(z).pipe(w);

读取流在读取完毕时会触发end事件,同时默认下pipe的option为{end:true},意味着在读取流的end事件中,默认调用了写入流的end()方法,即关闭了写入流。如果我们想在读取的数据之外继续写点东西,就要设置它为false,然后在reader的end事件中显式调用writer的end()事件。下面的例子就附加了"Goodbye \n"

reader.pipe(writer, { end: false });
reader.on('end', function() {
 writer.end('Goodbye\n');});


  • 另一个例子:

var fs = require('fs');
var file = fs.createWriteStream('./out.txt');

process.stdin.on('data', function(data) {
 file.write(data);});
process.stdin.on('end', function() {
 file.end();});
process.stdin.resume(); // stdin in paused by default

可以使用Pipe方法简化上面的写法:

var fs = require('fs');
process.stdin.pipe(fs.createWriteStream('./out.txt'));
process.stdin.resume();

  • 读取流接口是我们正在读取的数据的抽象,换句话说,这些数据i/o是一个读取流,我们可以看成读取流产生了数据。

  • 除非有侦听者,否则读取流不会真正读取数据。

  • 读取流有两个模式:流动模式 和 非流动模式。在流动(主动)模式,侦听处理过会直接获取到传入的数据;

        在非流动(被动)模式,侦听处理侦听处理过程中必须调用stream.read()才能获取到数据。

写入流Writable streams,下面的node核心对象都是写入流对象
Files fs.createWriteStream(path, [options])
HTTP (Server) http.ServerResponse
HTTP (Client) http.ClientRequest
TCP net.Socket
Child process child.stdin
Process process.stdoutA Writable Stream to stdout.
Process process.stderrA writable stream to stderr. Writes on this stream are blocking.
  • 写入流对象触发如下的事件:

Event: ’drain’当写入错误时,此事件提示可以再来搞一次
Event: ’error’写入发生错误时
  • 写入流有以下方法:

write(chunk, [encoding], [callback])
写入数据到指定的i/o,不指定encoding就是buffer,成功就返回true,失败反之。失败的话,会触发drain事件。
有成功、失败的返回,就是说write总是同步的?
end()终止写入,还未来得及写入的数据会继续写完。
destroy()终止写入,还未来得及写入的数据不会继续写。


// Write the data to the supplied writable stream 1MM times.
//写入100万数据到指定的写入流,当某一次失败时,会触发drain事件,该事件有个once的收听,收听事件里会再调用一次write方法
// Be attentive to back-pressure.
function writeOneMillionTimes(writer, data, encoding, callback) {
 var i = 1000000;
 write();
 function write() {
   var ok = true;
   do {
     i -= 1;
     if (i === 0) {
       // last time!
       writer.write(data, encoding, callback);
     } else {
       // see if we should continue, or wait
       // don't pass the callback, because we're not done yet.
       ok = writer.write(data, encoding);
     }
   } while (i > 0 && ok);
   if (i > 0) {
     // had to stop early!
     // write some more once it drains
     writer.once('drain', write);
   }
 }}

writable.end([chunk], [encoding], [callback])
  • chunk 

  • encoding

  • callback

当没有数据需要再写入时,调用此方法表示写入结束。callback回调类似于写在finish事件中。

在end方法之后对同一个写入流调用write方法将出错。

// write 'hello, ' and then end with 'world!'
http.createServer(function (req, res) {
 res.write('hello, ');
 res.end('world!');
 // writing more now is not allowed!});

Event: 'finish'

当end方法调用时,所有的数据都写入到目标i/o,这个事件被触发。

var writer = getWritableStreamSomehow();
for (var i = 0; i < 100; i ++) {
    writer.write('hello, #' + i + '!\n');}
    writer.end('this is the end\n');
    writer.on('finish', function() {
 console.error('all writes are now complete.');});

Event: 'pipe'
  • 读取流调用Pipe方法关联到某个写入流时,写入流被触发该事件。

var writer = getWritableStreamSomehow();

var reader = getReadableStreamSomehow();

writer.on('pipe', function(src) {
 console.error('something is piping into the writer');
 assert.equal(src, reader);});
reader.pipe(writer);



Event: 'unpipe'
var writer = getWritableStreamSomehow();var reader = getReadableStreamSomehow();
writer.on('unpipe', function(src) {
 console.error('something has stopped piping into the writer');
 assert.equal(src, reader);});
reader.pipe(writer);
reader.unpipe(writer);

Event: 'error'#


转载于:https://my.oschina.net/u/1753171/blog/293815

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值