极简 Node.js 入门 - 4.2 初识 stream
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node
stream 概念
Node.js 诞生是为了解决 I/O 密集的 Web 性能问题,最常使用的两个模块就是文件系统和网络,而这两个模块都是 stream 的重度用户,stream 是 Node.js 从入门到进阶的必经之路
Node.js 对 stream 是这样解释的
A stream is an abstract interface for working with streaming data in Node.js. The
stream
module provides an API for implementing the stream interface.
翻译过来流是 Node.js 中处理流式数据的抽象接口。 stream
模块提供了用于实现流接口的对象。基本就是用 stream 解释自己,第一次使用肯定不理解,但其实我们平时经常用到流
ls | grep *.js
命令的含义是 list 当前目录文件,把结果交给 grep 命令,按行筛选出拓展名是 js 的内容
使用 |
连接两条命令,把前一个命令的结果作为后一个命令的参数传入,这样数据像是水流在管道中传递,每个命令类似一个处理器,对数据做一些加工,因此 |
被称为“管道符”,这个处理过程就是流
从术语上讲流是对输入输出设备的抽象,是一组有序的、有起点和终点的字节数据传输手段
Node.js stream 类型
从程序角度而言流是有方向的数据,以程序为第一视角,按照流动方向可以分为三种流
- 设备流向程序:readable
- 程序流向设备:writable
- 双向流动:duplex、transform
NodeJS 关于流的操作被封装到了 Stream 模块,这个模块也被多个核心模块所引用。按照 Unix 的哲学:一切皆文件
- 普通文件(txt、jpg、mp4)
- 设备文件(stdin、stdout)
- 网络文件(http、net)
在 Node.js 中对文件的处理多数使用流来完成
小试牛刀
假设写程序某个功能需要读取某个配置文件 config.json,这时候简单分析一下
- 数据:config.json 的内容
- 方向:设备(物理磁盘文件) -> NodeJS 程序
应该使用 readable 流来做此事(后面章节会具体介绍 API)
const fs = require('fs');
const FILEPATH = '...';
const rs = fs.createReadStream(FILEPATH);
通过 fs 模块提供的 createReadStream() 方法创建了一个可读的流,这时候 _config.json_的内容从设备流向程序。示例并没有直接使用 Stream 模块,因为 fs 内部已经引用了 Stream 模块,并做了封装
读取到数据后可以对数据进行处理,比如需要写到某个路径 DEST ,这时候需要一个 writable 的流,让数据从程序流向设备
const ws = fs.createWriteStream(DEST);
两种流都有了,也就是两个数据加工器,那么如何通过类似 Unix 的管道符** | **来连接流呢?在 Node.js 中管道符号就是 pipe() 方法
const fs = require('fs');
const FILEPATH = 'DEST_PATH_IN_YOUR_DISK';
const rs = fs.createReadStream(FILEPATH);
const ws = fs.createWriteStream(DEST);
rs.pipe(ws);
这样利用流实现了简单的文件复制功能,关于 pipe() 方法的实现原理后面会提到,但有个值得注意地方:数据必须是从上游 pipe 到下游,也就是从一个 readable 流 pipe 到 writable 流
加工一下数据
上面提到了 readable 和 writable 的流称之为加工器,其实并不太恰当,因为过程中并没有加工什么,只是读取数据,然后存储数据
如果有个需求,把本地一个 package.json 文件中的所有字母都改为小写,并保存到同目录下的_ package-lower.json_ 文件下
这时候就需要用到双向的流了,假定有一个专门处理字符转小写的流 lowercase
,那么代码写出来大概是这样的
const fs = require('fs');
const rs = fs.createReadStream('./package.json');
const ws = fs.createWriteStream('./package-lower.json');
rs.pipe(lowercase).pipe(ws);
到这里就和清楚为什么称 pipe() 连接的流为加工器了,根据上面说的,必须从一个 readable 流 pipe 到 writable 流:
- rs -> lowercase:lowercase 在下游,所以 lower 需要是个 writable 流
- lowercase -> ws:相对而言,lowercase 又在上游,所以 lower 需要是个 readable 流
因此能够满足需求的 lowercase 必须是双向的流,具体使用 duplex 还是 transform 后面章节会提到。当然如果需求还有额外一些处理动作,比如字母还需要转成 ASCII 码,假定有一个双工流 ascii,那么代码简单改写
rs.pipe(lowercase).pipe(acsii).pipe(ws);
这样就完成了 package.json 文件字母转小写后处理成 acsii 码的需求
为什么应该使用 stream
有个用户 Web 在线看视频的场景,假定我们通过 HTTP 请求返回给用户电影内容,那么代码可能写成这样
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.readFile(moviePath, (err, data) => {
res.end(data);
});
}).listen(8080);
这样的代码又两个明显的问题
- 电影文件需要读完之后才能返回给客户,等待时间超长
- 电影文件需要一次放入内存中,内存吃不消
使用 stream 可以把电影文件一点点的放入内存中,然后一点点的返回给客户(利用了 HTTP 协议的 Transfer-Encoding: chunked 分段传输特性),用户体验得到优化,同时对内存的开销明显下降
const http = require('http');
const fs = require('fs');
http.createServer((req, res) => {
fs.createReadStream(moviePath).pipe(res);
}).listen(8080);
除了上述好处,stream 还让代码优雅了很多,功能逻辑独立,拓展也比较简单。比如需要对视频内容压缩,我们可以引入一个专门做此事的流,这个流不用关心其它部分做了什么,只要是接入管道中就可以了
const http = require('http');
const fs = require('fs');
const oppressor = require(oppressor);
http.createServer((req, res) => {
fs.createReadStream(moviePath)
.pipe(oppressor)
.pipe(res);
}).listen(8080);
可以看出来使用流后代码逻辑变得相对独立,可维护性也会有一定的改善
极简 Node.js 入门 - 4.2 初识 stream的更多相关文章
- 极简 Node.js 入门 - Node.js 是什么、性能有优势?
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 1.2 模块系统
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 1.3 调试
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 1.4 NPM & package.json
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 2.1 Path
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 2.2 事件
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 2.3 process
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 2.4 定时器
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 极简 Node.js 入门 - 3.1 File System API 风格
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
随机推荐
- redis安装及性能测试
Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库.通常被称为数据结构服务器,因为值(value)可以是 字符串(Stri ...
- ASP.NET Core 3.1 WebAPI的跨域问题
1.nuget要加上 Microsoft.AspNetCore.Cors 中间件. 2.在Startup类里先定义一个全局变量. private readonly string AllowSpecif ...
- 焦大:seo思维进化论(下)
http://www.wocaoseo.com/thread-50-1-1.html 很多东西在不同地方其所有的价值和意义是不一样的,seo亦是如此.在seo操作中我觉得最核心的就是检索价值观和用户需 ...
- 使用tensorflow2识别4位验证码及思考总结
在学习了CNN之后,自己想去做一个验证码识别,网上找了很多资料,杂七杂八的一大堆,但是好多是tf1写的,对tf1不太熟悉,有点看不懂,于是自己去摸索吧. 摸索的过程是异常艰难呀,一开始我直接用capt ...
- MarkDown编辑器中改变文本字体颜色大小
法一 有点类似前端里 <font face="微软雅黑" size=4 color=red>输入的文字</font> 其中 face对应字体 size 大小 ...
- csp201909-2小明种苹果续
/* 定义输入N 二维数组 输出T总数 D掉落棵树 E掉落组数 定义last记录上次掉落的编号,flag=1表示两次连续掉落,不掉落归零 spec=1表示1 2都掉落了,spec=2表示只有1掉落 对 ...
- Number(),parseInt()和parseFloat
一.Number() 1.如果是传进去数字值,只进行传入和传出,前置为 0x 的数字 和 前置 为0且不包含数字8,9的数字 ,会被转为十进制,对于其他的数字来说通常没有变化. 2.如果传进去 ...
- URL的字符编码
摘要: 在通过URL访问HTTP SERVER的时候,通常会产生trace callback的异常,返回505的错误," VERSION IS NOT SUPPORTED ?" , ...
- 你可能不了解的java枚举
枚举在java里也算个老生长谈的内容了,每当遇到一组需要类举的数据时我们都会自然而然地使用枚举类型: public enum Color { RED, GREEN, BLUE, YELLOW; pub ...
- Sql 注入----学习笔记
先了解下CRLF,CRLF常用在分隔符之间,CR是carriage retum(ASCII 13,\r) LF是Line Feed (ASCII 10,\n), \r\n这两个字符类似于回车是用于换行 ...