关于node.js中流的理解

什么是流

流是一个在node中与流数据工作的抽象接口,stream模块提供了一个基本的API,使得比较容易创建一个实现了流的接口的对象。node.js中提供了很多的流对象,例如,一个发向http服务器的请求,process.stdout都是流的实例。

流可以是可读的,可写或者两者兼备。所有的流都是EventEmitter的实例。

流的类型

在node中有4种基本的流类型:

1. Writable(可写流):可以把数据写进流里

2. Readable (可读流): 在该流上可以读取数据

3. Duplex (双工流): 在该流上,既可以写数据,又可以读取数据

4. Transform (转换流): 当数据在被写或被读的时候,可以修改数据的格式(例如压缩)

对象模式

node创建的所有流都是对字符串或者是Buffer对象操作。然而,也有与js的其他类型值工作的流实现。这些流被视作为在对象模式里操作。

当流在被创建的时候,使用了objectMode选项,流的实例就被转换为对象模式。试着去转换一个流为对象模式,这并不是安全的。

Buffering(数据缓冲中...) 

所有的writable和readable流将会把数据存储在内部的buffer中,这数据可以分别使用writable.writableBuffer或者readable.redableBuffer取得。

一定数量的数据缓冲能力取决于传递进流构造器中的highWaterMark选项。对于正常的流来说,highWaterMark选项明确了字节的数据量。对于在对象模式中操作的流,highWaterMark明确了对象的数量。

当实例调用stream.push(chunk)数据缓冲在readable流中,如果流的消费者没有调用stream.read(),数据将会待在内部队列直到被消费。

一旦内部读取的buffer的数据量达到了highWaterMark标明的阀值,流将会暂时的停止从源头读取数据,直到缓冲的数据能被消费的时候会再次读取

当 writable.write(chunk)方法被重复调用的时候,数据被缓冲在writable流里。当内部写的缓冲数据的总数量低于highWaterMark标明的阀值,调用writable.write()将会返回true,一旦内部缓冲的数据量达到或者超过highWaterMark时,将会返回false。

stream.pipe()是为了限制数据的缓冲在一个可接受的水平,使得源头和目的地不同的缓冲速度将不会那冲垮空闲的内存。

因为Duplex和Transform流都是可读且可写的,每个内部都维护了两个独立的内部缓冲用于读和写,使得每一边在维护一个合适且高效的数据流时都能够独立的进行操作。

可读流

可读流是一种数据被消费的源头的抽象,所有的可读流实例实现了stream.Readable定义的接口。

实例如下,首先创建了一个可读流,传入要读的文件位置还有一些读取设置,从哪里开始读,设置缓冲过程中读取的最大阀值highWaterMark,打开文件时,数据被读取流读取到缓冲中,当定义一个data事件处理器时,此时缓冲中的数据被消费,当流中无数据时或者数据读取完毕时会触发end事件,之后关闭文件。

    let fs = require('fs');

    let path = require('path');


    let rs = fs.createReadStream(path.join(__dirname, '1.txt'), {

        flags: 'r', // 读取操作

        encoding: 'utf8', // 默认为null,null代表的返回buffer

        autoClose: true, // 读取完自动关闭

        highWaterMark: 3, // 默认是64k, 64*1024b

        start: 3, // 从第3个字节开始读取

        end: 8 // 包括索引8

    });

    // 也可以手动设置编码或者在options声明

    // rs.setEncoding('utf8');


    rs.on('open', function() {

        console.log('文件打开了');

    });


    // 当流或其底层资源被关闭时触发

    rs.on('close', function() {

        console.log('文件关闭了');

    });


    rs.on('error', function(err) {

        console.log(err);

    });


    // 当流将数据块传送给消费者后触发

    rs.on('data', function(data) {

        console.log('data', data);

        // 停止触发data事件,暂停读取

        rs.pause();

        setTimeout(function() {

            // 恢复读取,暂停模式(需显示调用stream.read())切换为流动模式(数据自动从底层系统读取,并通过EventEmitter接口的事件尽可能快的被提供给应用程序)

            rs.resume();

            }, 1000);

        });


        // 表明流有新动态,要么有新的数据,要么到达流的尽头,若同时使用data事件,当调用rs.read方法才会触发data事件

        // rs.on('readable', function() {

        // console.log('readable data', rs.read());

    // });


    // 流中无数据时或者读取数据完毕触发

    rs.on('end', function() {

        console.log('end');

    });


    // 文件打开了

    // data 456

    // data 789

    // end

    // 文件关闭了 

两种读取模式

可读流有两种读取的模式:暂停模式和流动模式。这些模式与对象流模式不同,一个可读流可以是对象流,也可以不是也不管它是处在流动模式下还是暂停模式下。

1. 在流动模式下,数据被系统自动读取并且使用EventEmitter接口能够尽可能快的提供给应用消费

2. 在暂停模式中,stream.read()必须被显示调用,才能够从流中读取数据块。

所有的可读流开始处于一个暂停模式但是可以切换为流动模式通过以下一种方式:

1. 添加一个data事件处理器

2. 调用stream.resume()方法

3. 调用stream.pipe(),使得数据是可写的

所有的可读流可以切换为暂停模式,通过以下一种方式:

1. 如果没有管道的目的地,通过调用stream.pause()

2. 如果有管道的目的地,通过移除所有管道的目的地。大多数管道的目的地能够通过调用stream.unpipe()移除

最重要的是要明白readable直到有消费或者忽略数据被提供,才会产生数据。如果消费机制被带走了或者被停止了,那么readable会停止产生数据。

出于背后兼容性的原因,移除data事件处理器并不会自动的暂停流。统一,如果有管道的目的地,调用stream.pause()也不会保证流会暂停。

如果readable切换为流动模式,并且没有消费者去消费处理数据,数据将会丢失。

添加readable的事件处理器可以自动的使得流停止流动,数据可以通过readable.read()去消费。如果readable的事件处理器被移除,流可以再次流动如果有个data事件处理器的话

可写流 

可写流是数据被写入的目的地的抽象,所有的可写流实例实现了stream.Writable定义的接口

以下是我写的一个例子,首先创建了一个可写流对象,传入一个参数为需要写入的文件位置,一个参数是配置参数,highWaterMark是在写入的时候指定写入数据的阈值,若内部缓冲小于阈值时,会返回true,若返回false,应该停止写入数据,此时缓冲区已经达到阈值,满了,若所有缓冲的数据块被消费完了,清空了会触发一个drain事件。


    let fs = require('fs');

    let path = require('path');

    let ws = fs.createWriteStream(path.join(__dirname, '2.txt'), {

         flags: 'w',

         encoding: 'utf8',

         mode: 0o666,

         autoClose: true,

         start: 0,

         highWaterMark: 3 // 最高水平线,指定了字节总数

     });


    // 内部的缓冲小于创建的配置的highWaterMark,返回true

    let flag = ws.write('0', 'utf8', ()=>{});

    console.log(flag); // true


    flag = ws.write('1', 'utf8', ()=>{});

    console.log(flag); // true


    // 若返回false,则应该停止向流写入数据

    flag = ws.write('2', 'utf8', ()=>{});

    console.log(flag); // 返回false,写完3时,缓冲区已达到highWaterMark:3时,表明缓存区满了


    // 所有缓冲的数据块都被排空了,当达到highWaterMark时,表明缓存区满了,满了后被清空了才会触发drain

    ws.on('drain', function() {

    console.log('drain');

    flag = ws.write('3', 'utf8', ()=>{});

    console.log(flag); // true

    });


    // 可写流和可读流都会在内部的缓冲器中存储数据 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 好种子开花: 1,大宝今天幼儿园新年第一天,昨晚就跟她约定好今天去幼儿园,而且能否不赖床,她很愉快的答应了。今天早...
    成长中的小蜗牛阅读 219评论 1 1
  • 1.树和二叉树的定义 (1) 树的定义 树是n (n≥0) 个结点的有限集。 n=0 时称为空树。在任意一棵非空树...
    yinxmm阅读 2,369评论 0 3
  • 第四十六章 寝院起火 “离潇,殿下什么时候回来的?” “大概半个时辰之前,我刚从校场出来,就听见何将军他们说,殿...
    锦歌长安阅读 636评论 2 7
  • 有一天,一只猪正在森林里散步,忽然听见有人在喊“救命啊!救命啊!快救救我啊!”猪急忙向前跑,看见了一口井,...
    菲儿1225阅读 240评论 0 1