阅读目录

一: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(流)的更多相关文章

  1. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

  2. [NodeJs系列][译]理解NodeJs中的Event Loop、Timers以及process.nextTick()

    译者注: 为什么要翻译?其实在翻译这篇文章前,笔者有Google了一下中文翻译,看的不是很明白,所以才有自己翻译的打算,当然能力有限,文中或有错漏,欢迎指正. 文末会有几个小问题,大家不妨一起思考一下 ...

  3. java中的Stream流

    java中的Stream流 说到Stream便容易想到I/O Stream,而实际上,谁规定"流"就一定是"IO流"呢?在Java 8中,得益于Lambda所带 ...

  4. Java8中的Stream流式操作 - 入门篇

    作者:汤圆 个人博客:javalover.cc 前言 之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧 因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一 ...

  5. Node 中的 stream (流)

    流的概念 流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface). stream 模块提供了基础的 API .使用这些 API 可以很容易地来构建实现流 ...

  6. 深入理解nodejs中的异步编程

    目录 简介 同步异步和阻塞非阻塞 javascript中的回调 回调函数的错误处理 回调地狱 ES6中的Promise 什么是Promise Promise的特点 Promise的优点 Promise ...

  7. 双层for循环用java中的stream流来实现

    //双重for循环for (int i = 0; i < fusRecomConfigDOList.size(); i++) { for (int j = 0; j < fusRecomC ...

  8. 理解 nodeJS 中的 buffer,stream

    在Node.js开发中,当遇到 buffer,stream,和二进制数据处理时,你是否像我一样,总是感到困惑?这种感觉是否会让你认为不了解它们,以为它们不适合你,认为而这些是Node.js作者们的事情 ...

  9. 77.深入理解nodejs中Express的中间件

    转自:https://blog.csdn.net/huang100qi/article/details/80220012 Express是一个基于Node.js平台的web应用开发框架,在Node.j ...

随机推荐

  1. C/C++应用程序内存泄漏检查统计方案

    一.前绪 C/C++程序给某些程序员的几大印象之一就是内存自己管理容易泄漏容易崩,笔者曾经在一个产品中使用C语言开发维护部分模块,只要产品有内存泄漏和崩溃的问题,就被甩锅“我的程序是C#开发的内存都是 ...

  2. Spring Boot2(九):整合Jpa的基本使用

    一.前言 今天早上看到一篇微信文章,说的是国内普遍用的Mybatis,而国外确普遍用的是Jpa.我之前也看了jpa,发现入门相当容易.jpa对于简单的CRUD支持非常好,开发效率也会比Mybatis高 ...

  3. centos7搭建基于SAMBA的网络存储

    学习目标: 通过本实验理解Linux系统下SAMBA服务器和客户端的配置,实现客户机可自动挂载服务端的共享存储. 操作步骤: 1.  SAMBA服务器搭建 2.  SAMBA客户端配置 参考命令:   ...

  4. 并发编程-concurrent指南-Lock-可重入锁(ReentrantLock)

    可重入和不可重入的概念是这样的:当一个线程获得了当前实例的锁,并进入方法A,这个线程在没有释放这把锁的时候,能否再次进入方法A呢? 可重入锁:可以再次进入方法A,就是说在释放锁前此线程可以再次进入方法 ...

  5. 常用的方法论-NPS

  6. UVALive 7037:The Problem Needs 3D Arrays(最大密度子图)

    题目链接 题意 给出n个点,每个点有一个值,现在要选择一些点的集合,使得(选择的点生成的逆序对数目)/(选择的点的数量)的比率最大. 思路 点与点之间生成一个逆序对可以看做是得到一个边,那么就是分数规 ...

  7. 实例解说AngularJS在自动化测试中的应用

    一.什么是AngularJS ? 1.AngularJS是一组用来开发web页面的框架.模板以及数据绑定和丰富UI的组件: 2.AngularJS提供了一系列健壮的功能,以及将代码隔离成模块的方法: ...

  8. Hash的应用2

    代码: #include <stdio.h> #define OFFSET 500000//偏移量 ];//记录每个数是否出现,出现为1,不出现为0 int main(){ int n,m ...

  9. Kafka【入门】就这一篇!

    为获得更好的阅读体验,建议您访问原文地址:传送门 前言:在之前的文章里面已经了解到了「消息队列」是怎么样的一种存在(传送门),Kafka 作为当下流行的一种中间件,我们现在开始学习它! 一.Kafka ...

  10. ASP.NET--Repeater控件分页功能实现

    这两天由于‘销售渠道’系统需要实现新功能,开发了三个页面,三个界面功能大致相同. 功能:分页显示特定sql查询结果,点击上一页下一页均可显示.单击某记录可以选定修改某特定字段<DropDownL ...