流在我看来是一门比较高深的学问,不可能轻易的就掌握并且总结完。这里仅仅是对stream的入门介绍,大家在项目中遇到的问题肯定不是简单的使用就能解决的,还是需要多看文档进行复习。
-
什么是流?
流是数据的集合,就像数组和字符串。
其中的差别就是流是不会一次性全部获取到的,不然为什么叫做流。
像我们以前碰到的堆栈,其实是在内存中去划分了一块区域去存放这些变量的。但是流是不必匹配内存指针的,它就想一个管道,里面流动着数据。这让其在处理大容量数据的时候变得格外强大(尤其是性能上的节约)。
然而流不仅仅可以处理大容量数据,它也给予了我们在代码中的组合能力。例如pipe,他可以链接两个流的源头,使其传输。
就像大多数核心模块都是继承于events一样,流也是核心模块的一个超级基石。
上面的原生对象都是可以成为“源头”的。他们中有一些,可读也可写。例如fs,zlib,crypto/tcp sockets等。在node中有4种基本的流类型
- 可读流 fs.createReadStream
- 可写流 fs.createWriteStream
- 双向流 TCP sockets
- 转换流 zlib.createGzip
可读流就是可以放出资源的抽象描述,可写流也是可以盛放资源的描述。
双向流既可以放出,也可以盛放。
转换流是一种特殊的双向流。你可以将其视为中间处理者。他可以接入一个可读流,处理数据后,放出可写流。
所有流都是EventEmitter的实例。触发这些实例事件可以读或者写数据。然而我们可以用最为轻松的方式pipe来处理这些流。
Fs.createReadStream.pipe(fs.createWtiteStream)
Pipe看起来更像一个管道,将两个流连接起来,更为神奇的是他们能够链式调用。这样看起来就更像一个管道了!我们需要注意的是当我们在使用pipe处理流的方式时,就不要再使用事件处理了,防止出现一些难处理的情况。
以上是最基本的入门,接下来我们进入官方文档的世界去探寻一些api的使用
1. 应用程序中使用流时需要的 API
-
缓冲
无论是可读流还是可写流,都会在内部缓冲器中存储好自己要发送的数据
writable.writableBuffer 或 readable.readableBuffer可以获取到他们
fs.createWriteStream().writableBuffer
可以缓冲的数据大小取决于传入流的构造函数的highWaterMark参数。
对于普通的流, highWaterMark 指定了字节的总数。 对于对象模式的流, highWaterMark 指定了对象的总数。
当调用 stream.push(chunk) 时,数据会被缓冲在可读流的内部缓冲器中。如果可写流没有调用stream.read(),则数据会保留在内直到某个可写流来读取。
一旦内部缓冲器存的值达到 highWaterMark指定的值时,流会暂时停止从底层资源读取数据,直到当前缓冲的数据被某个可写流读取了
当调用 writable.write(chunk) 时,数据会被缓冲在可写流中。 当内部的可写缓冲的总大小小于 highWaterMark 设置值时,调用 writable.write() 会返回 true。 一旦内部缓冲的大小达到或超过 highWaterMark 时,则会返回 false。
此处的writable 或者 上面的readble都代表着某个可写/可读的实例,都继承了stram类。
stram的到来就是为了限制数据的缓存大小,防止你的内存崩溃。
-
你的node应用离不开stream
const http = require('http');
const server = http.createServer((req, res) => {
// req 是一个 http.IncomingMessage 实例,它是可读流。
// res 是一个 http.ServerResponse 实例,它是可写流。
let body = '';
// 接收数据为 utf8 字符串,
// 如果没有设置字符编码,则会接收到 Buffer 对象。
req.setEncoding('utf8');
// 如果添加了监听器,则可读流会触发 'data' 事件。
req.on('data', (chunk) => {
body += chunk;
});
// 'end' 事件表明整个请求体已被接收。
req.on('end', () => {
try {
const data = JSON.parse(body);
// 响应信息给用户。
res.write(typeof data);
res.end();
} catch (er) {
// json 解析失败。
res.statusCode = 400;
return res.end(`错误: ${er.message}`);
}
});
});
server.listen(1337);
可写流(比如例子中的 res)会暴露了一些方法,比如 write() 和 end() 用于写入数据到流。
当数据可以从流读取时,可读流会使用触发器。 从流读取数据的方式有很多种。
可写流和可读流都通过多种方式使用 监听器/触发器 来通讯流的当前状态。
Duplex 流和 Transform 流都是可写又可读的。
对于只需写入数据到流或从流消费数据的应用程序通常不需要调用 require('stream')。因为他们已经实现了它的特性
对于需要实现新类型的流的开发者,可以参见用于实现流的API章节。