nodejs-流(stream)操作基础

什么是流?
  • 流是一种用来处理文件的字节传输手段
  • 它不关心文件的整体内容,只关注是否从文件中读取到了数据,以及读到数据之后的处理
流的类型?
  • Readable--> 可读流(例如 fs.createReadStream())
  • Writable--> 可写流(例如 fs.createWriteStream())
  • Duplex--> 可读可写流(双工流 例如 net.Socket)
  • Transform--> 在读写过程中可以修改和变化数据的Duplex流(例如 zlib.createDeflate())
Reanable(可读流)

创建可读流

var rs = fs.createReadStream(path,{
    flags: 'r', // 打开文件要做的操作,默认为‘r’
    encoding: 'utf8', // 指定解析的字符编码格式,默认为null; 同理:rs.setEncoding('utf8')
    start: '3', // 开始读取的索引位置
    end: '9', // 结束读取的索引位置(包括结束位置)
    highWaterMark: '3', // 从底层资源读取数据并存储在内部缓冲区中的最大字节数,默认16k;
                        //注意:如果指定utf8编码highWaterMark要大于3个字节
});

open事件

// 打开要读取的文件后触发
rs.on('open', () => {
    console.log('打开文件')
})

data事件

// 流自动从底层读取数据
rs.on('data', chunk => {
    console.log(chunk);
    // 调用pause()方法暂停数据的读取
    rs.pause(); // 此时切换为暂停模式
})
setTimeout(() => {
    // 恢复数据的读取,切换回流动模式
    rs.resume();
}, 2000)

error事件

// 读取数据过程中出现错误触发
rs.on('error', err => {
    console.log(err);
})

end事件

// 数据读取完毕的时候触发
rs.on('end', () => {
    console.log('读取结束');
})

close事件

rs.on('close', () => {
    console.log('读取完毕后关闭文件');
})

readable事件

rs.on('readable', () => {
    // 监听readable事件切换为暂停模式,调用read()方法读取流中缓存的数据
    // 当缓存中的数据不够highWaterMark,重新向底层读取highWaterMark字节的数据填充缓存区;
    // 缓存区的字节数可能会大于highWaterMark
    var chunk = rs.read(1)
    console.log(chunk);
})

可读流分为两种模式:

  • flowing(流动模式):
    当可读流监听"data"事件的时候,当前流为流动模式;可读流自动从系统底层读取数据,并通过EventEmitter发送事件来将数据提供给应用。

  • paused(暂停模式):
    当可读流监听"readable"事件的时候,当前流为暂停模式;必须要调用stream.read()方法从流中读取数据片段。

readable._readableState.flowing 字段的值来标识当前可读流是什么模式

  • null:值为null情况下,可读流将不会产生数据,因为数据不会被消费;在当前状态下,监听流的"data"事件即可变为流动模式
  • true:值为true的情况下,可读流为流动模式,流会自动读取数据返回
  • false:值为false的情况下,可读流为暂停模式,只有调用stream.read()方法才可以从流中读取数据
Writable(可写流)

创建可写流

var ws = fs.createWriteStream(path, {
    flags: 'w', // 打开文件要做的操作,默认是‘w’
    encoding: 'utf8', // 指定写入的字符编码格式
    highWaterMark: '3', // 缓存区大小(默认为16kb), 
});

write方法

* chunk 要写入的数据,类型为 buffer/string
* encoding 可选,chunk为字符串时,指定字符编码
* callback 写入完毕后的回调
var flag = ws.write(chunk, encoding, callback);
// flag 为布尔值,缓存区满时为false,否则为true

end方法

ws.end(chunk, encoding, callback);
// 结束写入的方法,在结束的时候还可以写一部分数据进去,
// callback 如果传入,它将作为finish事件的回调函数

drain事件

var flag = ws.write(chunk, encoding, callback);
// 当flag为false时,表示缓存区已满;当缓存区数据用完,缓存区清空的时候会触发drain事件
// 必须是在缓存区满了清空后才会触发drain事件
ws.on('drain', () => {
    console.log('缓存区已清空')
})

finish事件

ws.end('结束');
ws.on('finish', () => {
    console.log('所有写入完成');
})
// 在调用了 stream.end() 方法,且缓冲区数据都已经传给底层系统之后, 'finish' 事件将被触发。
pipe

Readable和Writable分别实现了对文件的读和写的操作;但是通常情况下会出现边读边写的场景,读取一个文件的内容,写入到另一个文件中;在这种场景下,可能会出现读写不均衡的问题,写入比较慢,读取比较快(来不及写入的文件数据可能会丢失);所以我们期待可以达到读写均衡的状态,于是出现了pipe(导流)。
它可以控制读取的速率,当写入较慢的时候暂停对文件的读取;当可写流缓存区数据写入完毕后恢复文件的读取。

pipe的用法

readStream.pipe(writeStream);
var from = fs.createReadStream('./1.txt');
var to = fs.createWriteStream('./2.txt');
from.pipe(to);
// pipe可以绑定多个可写流
var to2 = fs.createWriteStream('./3.txt');
from.pipe(to2);
// 分离from绑定的可写流;
// 不传参数的话会分离所有绑定的可写流
from.unpipe(to)

pipe原理

// 当可写流调用write()方法返回false时,表示缓存区已满,这时将可读流切换为暂停模式;
// 暂停读取数据,同时监听可写流的drain事件,当缓存区数据写入完毕,触发drain事件;
// 在drain事件的回调函数中切换可读流为流动模式继续读取数据
var fs = require('fs');
var ws = fs.createWriteStream('./2.txt');
var rs = fs.createReadStream('./1.txt');
rs.on('data', data => {
    var flag = ws.write(data);
    if(!flag)
    rs.pause();
});
ws.on('drain', () => {
    rs.resume();
});
rs.on('end', () => {
    ws.end();
});
Duplex(双工流)

Duplex 流是同时实现了 Readable 和 Writable 接口的流
其中Readable和Writable分别是两个不相关的流

// 实现一个简单自定义的duplex需要定义好两个方法read和write

let {Duplex} = require('stream');
let index = 0;
let s = Duplex({
    read(){
        console.log(index)
        if(index++<3){
            this.push('b');
        } else {
            this.push(null); 
        }
    },
    write(chunk,encoding,cb){
        var a = chunk.toString().toUpperCase()
        console.log(1);
        cb();
    }
});
//process.stdin 标准输入流
//proces.stdout标准输出流
process.stdin.pipe(s)
s.pipe(process.stdout)
process.stdin.pipe(s).pipe(process.stdout);
Transform(转换)

transform流也是一个双工流,用以处理输入输出是因果相关,位于管道中间层的 Transform 是即可读也可写的;
Transform类最初继承自stream.Duplex,并且实现了它自己版本的writable._write()和readable._read()方法。
自定义一个transform流必须实现transform() 方法;

let {Transform}  = require('stream');
//转换流是实现数据转换的
let t = Transform({
    transform(chunk,encoding,cb){
        this.push(chunk.toString().toUpperCase());
        cb();
    }
});
process.stdin.pipe(t).pipe(process.stdout);

Transform 同样是双工流,看起来和 Duplex 重复了,但两者有一个重要的区别:Duplex 虽然同时具备可读流和可写流,但两者是相对独立的;Transform 的一种流的数据会经过一定的处理过程自动进入另外一个流。


transform.png
参考:

深入理解 Node Stream 内部机制
nodejs中流(stream)的理解
基础
进阶
实战

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

推荐阅读更多精彩内容