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() |
| ||
多次读入内存(流) Partially buffered (streaming) |
|
|
上表的几种读取数据方法的区别在于是否同步读取、所分配的内存大小。一般我们就用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 |
|
使用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.stdout | A Writable Stream to stdout. |
Process process.stderr | A 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);