理解nodejs中的stream(流)
阅读目录
一:nodeJS中的stream(流)的概念及作用?
什么是流呢?日常生活中有水流,我们很容易想得到的就是水龙头,那么水龙头流出的水是有序且有方向的(从高处往低处流)。我们在nodejs中的流也是一样的,他们也是有序且有方向的。nodejs中的流是可读的、或可写的、或可读可写的。
并且流继承了EventEmitter。因此所有的流都是EventEmitter的实列。
Node.js中有四种基本的流类型,如下:
1. Readable--可读的流(比如 fs.createReadStream()).
2. Writable--可写的流(比如 fs.createWriteStream()).
3. Duplex--可读写的流
4. Transform---在读写过程中可以修改和变换数据的Duplex流。
nodeJS中的流最大的作用是:读取大文件的过程中,不会一次性的读入到内存中。每次只会读取数据源的一个数据块。
然后后续过程中可以立即处理该数据块(数据处理完成后会进入垃圾回收机制)。而不用等待所有的数据。
我们先来看一个简单的流的实列来理解下:
1. 首先我们来创建一个大文件,如下代码:
const fs = require('fs');
const file = fs.createWriteStream('./big.txt');
// 循环500万次
for (let i = 0; i <= 5000000; i++) {
file.write('我是空智,我来测试一个大文件, 你看看我会有多大?');
} file.end();
我在我项目文件里面新建一个app.js文件,然后把上面的代码放入到 app.js 里面去,可以看到循环了500万次后,写入500万次数据到 big.txt中去,因此会在文件目录下生成一个 big.txt文件,如下:
该文件在我磁盘中显示345兆。
readFile读取该文件:
下面我们使用 readFile 来读取该文件看看(readFile会一次性读入到内存中)。
我们把app.js代码改成如下:
const fs = require('fs');
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx, next) => {
const res = ctx.res;
fs.readFile('./big.txt', (err, data) => {
if (err) {
throw err;
} else {
res.end(data);
}
})
}); app.listen(3001, () => {
console.log('listening on 3001');
});
当我们运行node app.js 后,我们查看下该代码占用的内存(12MB)如下:
但是当我们运行 http://localhost:3001/ 后,发现占用的内存(有338MB了)如下:
readFile 它会把 big.txt的文件内容整个的读进以Buffer格式存入到内存中,然后再写进返回对象,那么这样的效率非常低的,并且如果该文件如果是1G或2G以上的文件,那么内存会直接被卡死掉的。或者服务器直接会奔溃掉。
下面我们使用 Node中的createReadStream方法就可以避免占用内存多的情况发生。我们把app.js 代码改成如下所示:
const fs = require('fs');
const Koa = require('koa'); const app = new Koa(); app.use(async(ctx, next) => {
const res = ctx.res;
const file = fs.createReadStream('./big.txt');
file.pipe(res);
}); app.listen(3001, () => {
console.log('listening on 3001');
});
然后我们继续查看内存的使用情况,如下所示:
可以看到我们的占用的内存只有12.8兆。也就是说:createReadStream 在读取大文件的过程中,不会一次性的读入到内存中。
每次只会读取数据源的一个数据块。这就是流的优点。下面我们来分别看下流吧。
二:fs.createReadStream() 可读流
其基本使用方法如下:
const fs = require('fs');
const rs = fs.createReadStream('./big.txt', {
flags: 'r', // 文件的操作方式,同readFile中的配置一样,这里默认是可读的是 r
encoding: 'utf-8', // 编码格式
autoClose: true, // 是否关闭读取文件操作系统内部使用的文件描述符
start: 0, // 开始读取的位置
end: 5, // 结束读取的位置
highWaterMark: 1 // 每次读取的个数
});
fs.createReadStream有以下监听事件:
具体有哪些事件可以查看官网(http://nodejs.cn/api/stream.html#stream_class_stream_readable) 这边先截图出来简单看看,如下所示:
有了上面这些监听方法,我们可以先看一个完整的实列,如下代码:
const fs = require('fs');
const file = fs.createReadStream('./msg.txt', {
flags: 'r', // 文件的操作方式,同readFile中的配置一样,这里默认是可读的是 r
encoding: 'utf-8', // 编码格式
autoClose: true, // 是否关闭读取文件操作系统内部使用的文件描述符
start: 0, // 开始读取的位置
end: 5, // 结束读取的位置
highWaterMark: 1 // 每次读取的个数
}); file.on('open', () => {
console.log('开始读取文件');
}); file.on('data', (data) => {
console.log('读取到的数据:');
console.log(data);
}); file.on('end', () => {
console.log('文件全部读取完毕');
}); file.on('close', () => {
console.log('文件被关闭');
}); file.on('error', (err) => {
console.log('读取文件失败');
});
执行如下图所示:
从上图我们可以看到,先打开文件,执行open事件,然后就是不断的触发data事件,等data事情读取结束后会触发end事件,然后会将文件关闭,触发close事件。
注意:msg.txt文件内容如下:hello world; 但是上面为什么只读了 hello了,那是因为我们上面限制了从开始读取位置读取,然后到结束位置结束(5). 并且限定了 highWaterMark: 1,每次读取的个数为1。当然如果我们改成每次读取的个数为2的话,那么每次会读2个字符。
pause() 方法:
如果我们在读取的过程中,想暂停事件的读取,我们可以使用 ReadStream对象的pause方法暂停data事件的触发。 如下代码:
file.on('data', (data) => {
console.log('读取到的数据:');
console.log(data);
file.pause();
});
然后如下图所示:
上面暂停了使用 pause()方法,如果我们现在想重新读取,需要使用 resume()方法,如下所示:
setTimeout(() => {
file.resume();
}, 100);
执行结果如下:
其他的一些事件,比如 readable事件等,可以看官方文档 (http://nodejs.cn/api/stream.html#stream_event_readable). 这里就不多分析了。
三:fs.createWriteStream() 可写流
如下代码演示:
const fs = require('fs');
const file = fs.createWriteStream('./1.txt', {
flags: 'w', // 文件的操作方式,同writeFile中的配置一样,这里默认是可读的是 w
encoding: 'utf-8', // 编码格式
autoClose: true, // 是否关闭读取文件操作系统内部使用的文件描述符
start: 0, // 开始读取的位置
highWaterMark: 1 // 每次写入的个数
}); let f1 = file.write('1', 'utf-8', () => {
console.log('写入成功1111');
}); f1 = file.write('2', 'utf-8', () => {
console.log('写入成功2222');
}); f1 = file.write('3', 'utf-8', () => {
console.log('写入成功3333');
}); // 标记文件末尾
file.end(); // 处理事件
file.on('finish', () => {
console.log('写入完成');
}); file.on('error', (err) => {
console.log(err);
});
在我项目的根目录下会生成一个 1.txt文件,里面有123内容。
详细请看官网(http://nodejs.cn/api/fs.html#fs_fs_writefile_file_data_options_callback)
管道流(pipe)
我们需要把我们上面可读流读到的数据需要放到可写流中去写入到文件里面去。我们可以如下操作代码:
const fs = require('fs'); // 读取msg.txt中的字符串 hello world
const msg = fs.createReadStream('./msg.txt', {
highWaterMark: 5
}); // 写入到1.txt中
const f1 = fs.createWriteStream('./1.txt', {
encoding: 'utf-8',
highWaterMark: 1
}); // 监听读取的数据过程,把读取的数据写入到我们的1.txt文件里面去
msg.on('data', (chunk) => {
f1.write(chunk, 'utf-8', () => {
console.log('写入成功');
});
});
但是实现如上的机制,我们可以使用管道机制,管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。如下图所示:
如上代码,我们可以改成如下代码:
const fs = require('fs'); // 读取msg.txt中的字符串 hello world
const msg = fs.createReadStream('./msg.txt', {
highWaterMark: 5
}); // 写入到1.txt中
const f1 = fs.createWriteStream('./1.txt', {
encoding: 'utf-8',
highWaterMark: 1
}); const res = msg.pipe(f1);
console.log(res);
如上打印 res后,我们在命令行中查看下基本信息如下:
理解nodejs中的stream(流)的更多相关文章
- 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?
写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...
- [NodeJs系列][译]理解NodeJs中的Event Loop、Timers以及process.nextTick()
译者注: 为什么要翻译?其实在翻译这篇文章前,笔者有Google了一下中文翻译,看的不是很明白,所以才有自己翻译的打算,当然能力有限,文中或有错漏,欢迎指正. 文末会有几个小问题,大家不妨一起思考一下 ...
- java中的Stream流
java中的Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 ...
- Java8中的Stream流式操作 - 入门篇
作者:汤圆 个人博客:javalover.cc 前言 之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧 因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一 ...
- Node 中的 stream (流)
流的概念 流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface). stream 模块提供了基础的 API .使用这些 API 可以很容易地来构建实现流 ...
- 深入理解nodejs中的异步编程
目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promise ...
- 双层for循环用java中的stream流来实现
//双重for循环for (int i = 0; i < fusRecomConfigDOList.size(); i++) { for (int j = 0; j < fusRecomC ...
- 理解 nodeJS 中的 buffer,stream
在Node.js开发中,当遇到 buffer,stream,和二进制数据处理时,你是否像我一样,总是感到困惑?这种感觉是否会让你认为不了解它们,以为它们不适合你,认为而这些是Node.js作者们的事情 ...
- 77.深入理解nodejs中Express的中间件
转自:https://blog.csdn.net/huang100qi/article/details/80220012 Express是一个基于Node.js平台的web应用开发框架,在Node.j ...
随机推荐
- 003-python基础-字符类型
基本数据类型(int,bool,str) 1.基本数据数据类型: int 整数 str 字符串. 一般不存放大量的数据 bool 布尔值. 用来判断. True, False list 列表.用来存放 ...
- os.path.dirname(__file__)
os.path.dirname(__file__) 返回脚本的路径 描述: 必须实际存在的.py文件,如果直接在命令行执行,则会引发异常NameError: name 'file' is not de ...
- C语言学习书籍推荐《C和指针 Pointers On C》下载
<C和指针 POINTERS ON C>提供与C语言编程相关的全面资源和深入讨论.本书通过对指针的基础知识和高 级特性的探讨,帮助程序员把指针的强大功能融入到自己的程序中去. 全书共18 ...
- vue-cli · Failed to download repo vuejs-templates/webpack: self signed certificate in certificate chain
vue init webpack <Project name> 报错: vue-cli · Failed to download repo vuejs-templates/webpack: ...
- Java - 格式化输出JSON字符串的两种方式
目录 1 使用阿里的fastjson 1.1 项目的pom.xml依赖 1.2 Java示例代码 2 使用谷歌的gson 2.1 项目的pom.xml依赖 2.2 Java示例代码 1 使用阿里的fa ...
- MyBatis从入门到精通:第一章配置MyBatis
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC ...
- 【POJ - 1995】Raising Modulo Numbers(快速幂)
-->Raising Modulo Numbers Descriptions: 题目一大堆,真没什么用,大致题意 Z M H A1 B1 A2 B2 A3 B3 ......... AH ...
- 网页判断浏览器是否安装flash
<script> //检验flash版本 以及falsh是否安装 function detectFlash() { try { if(navigator.mimeTypes.length& ...
- PJzhang:python基础入门的7个疗程-five
猫宁!!! 参考链接:易灵微课-21天轻松掌握零基础python入门必修课 https://www.liaoxuefeng.com/wiki/1016959663602400 第十三天:代码组织 类是 ...
- java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能
这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能 ...