3.STREAM

1.Stream使用介绍

WritableStream[event]
    "close","drain","error","finish","pipe","unpipe"
    可写流中的"drain"事件触发时机:如果调用stream.write(chunk)方法返回false,流将在适当的时机触发"drain"事件。这时才可以继续想流中写入数据。
    //向可写流中写入数据一百万次
    function writeMillionTimes(writer,data,encoding,callback){
        let i =1000000;
        write();
        function write(){
            var ok = true;
            //只要100万次没有执行完成,或者可以吸入是true的状态,就一直执行这个循环
            do{
                i--;
                if(i===0){
                    //最后一次写入数据,需要调用回调函数
                    writer.write(data,encoding,callback);
                    }else{
                        //检查是否可以继续写入,这里不要传递callback。因为还没有结束
                        ok = writer.write(data,encoding);                       
                        }               
                }while(i>0 && ok);
                //执行到这里来了,要么是执行结束了,要么是被流量太大给吓到了
                if(i >0){
                    //这里就是提前停下的处理路径
                    //"drain"事件触发后才可以继续写入
                    writer.once("drain",write);//当触发"drain"事件后,写入空了,可以继续写入数据了。
                    }
            }       
        }
ReadableStream[event]
    "close","data","error","end","error","readable"
    可读流中的"data"事件触发时机:[流将数据传递给消费者时触发:1.切换到flowing模式时触发[.pipe(),.resume()];2.调用reabale.read()方法时触发]
    可读流中的"end"事件触发时机:流中再米有数据可供消费是触发
    可读流中的"readable"事件触发时机:流中有数据可供读取时触发[读取到达流的尾部时,也会触发"readble"事件]
    可读流中的"close"事件触发时机:流或其底层资源关闭后触发,触发"close"后将不会再触发任何事件
    主要的方法介绍:
        readable.pause();将会使flowing模式的流停止触发"data"事件。退出flowing模式,任何可用的数据将会保存在内部缓存中
        const readble = getReadableStreamSomehow();
        readable.on("data",(chunk)=>{
                console.log(`Received ${chunk.length} bytes of data`);
                readable.pause();
                console.log("There will be no additional data for 1 second");
                setTimeout(()=>{
                        console.log("Now data will start flowing again");
                        readable.resume();
                    },1000);
            });
        readable.pipe(destination[,options]);将可写流自动切换到flowing模式,数据流有自动限速的功能。就是把可读的数据流,写到,可写的数据流中去。  
 
        const readable = getReadableStreamSomehow();
        const writable = getWritableStreamSomehow("file.txt");
        //readable中的数据传给了file.txt文件
        readable.pipe(writable);
        
        默认情况下,源数据流触发end时,目标数据流也会触发end。我们可以在options中配置end为false
        readable.read([size]); 根据size选项返回数据内容。如果没有size选项将会返回所有的内容。
            
 总体介绍:Streams can be readable, writable, or both. All streams are instances of EventEmitter.
 引入模块:const stream = require("stream");
    -Readable可读流
    -Writable可写流
    -Duplex可读写的流
    -Transform在读写过程中可以修改和变换数据的流
    
①所有创建的流对象只能操作strings和Buffer对象
    -可写流通过反复调用writeable.write(chunk)方法将数据放到缓存中。
        当内部可写缓存总大小<highWaterMark的时候,调用返回的是true
        当内部缓存的大小达到或超过highWaterMark时,调用返回的是false
    -stream的关键目标是,限制缓存数据的大小,匹配读写速度。
    
②可写流的举例Writable
    -HTTP requests on the client
    -HTTP responses on the server
    -fs write streams
    -zlib streams
    -crypto streams
    -TCP sockets
    -child process stdin
    -process.stdout,process.stderr
    所有的可写流基本使用模式如下:
        const myStream = getWriteStreamSomehow();
        myStream.write("some data");        
        myStream.write("some more data");
        myStream.end("done writing data");
        
③class stream.Writable
    -"close" event将在底层资源关闭后触发
    -"drain" event如果调用stream.write(chunk)返回false时触发
    -"error" event当写入数据出错或者使用管道出错时触发。此时回调函数仅接收一个Error对象,此时需要自己手动关闭流
    -"finish" event在stream.end()后触发事件[缓冲区的数据传给了底层系统后]
    -"pipe" event可读流上调用stream.pipe()方法,并在目标流向中添加当前可写流时触发
        const writer = getWriteableStreamSomehow();
        const reader = getReadableStreamSomehow();
        writer.on("pipe",(src)=>{
            console.error("something is piping into the writer");
            assert.equal(src,reader);
            });
        write.on("unpipe",(src)=>{
            console.error("something is unpiping into the writer");
            assert(src,reader);
            });         
        reader.pipe(writer);    
        reader.unpipe(writer);
    -writable.end();表明接下来没有数据要被写入Writable。如果有回调函数,将作为"finish"事件的回调函数
        注意,在调用stream.end()方法后,再调用stream.write()方法将会导致出错
    -writable.write(chunk);返回的是boolean。向流中写入数据,数据处理完后再调用callback
                            
④可读流的举例Readable
    -HTTP responses on the client
    -HTTP request on the server
    -fs read streams
    -zib streams
    -crypto streams
    -TCP sockets
    -child process stdout and stderr
    -process.stdin
可读流有两种模式:
    -flowing模式,可读流自动从系统底层读取数据
    -paused模式,需要显示调用stream.read()从流中读取数据片段
    
    两种模式的相互转换
        -paused->flowing模式,"data"event,stream.resume(),stream.pipe()
        -flowing->paused模式,stream.pause(),stream.unpipe()
⑤class stream.Readble
    -"close" event 底层资源关闭后触发
    -"data" event 
    -"end" event 流中没有可以消费的数据时触发
    -"error" event监听错误
    -"readable" event流中有数据可供读取时触发
        const readable = getReadableStreamSomehow();
        readable.on("data",(chunk)=>{
                console.log(`Received ${chunk.length} bytes of data`);
            });
        readable.on("end",()=>{
                console.log("There will be no more data");          
            });
    
    -readable.pipe(destination[,options]);将一个Writable流绑定到readable上。
        const readable = getReadleStreamSomehow();
        const writable = fs.createWriteStream("file.txt");
        //readable中的所有数据都传给"file.txt"
        readable.pipe(writable);
        
        可以在单个可读流上绑定多个可写流,进行链式管道操作
        const r = fs.createReadStream("file.txt");
        cosnt z = zlib.createGzip();
        const w = fs.createWriteStream("file.txt.gz");
        r.pipe(z).pipe(w);
        
        值得注意的是,源可读流触发"end"事件时,目标流也会调用stream.end()方法。可在option中指定end为false
    -readable.read([size])  size选项指定了需要读取多少数据,不指定size将会返回所有内容
        const readable = getReadableStreamSomehow();
        readable.on("readable",()=>{
                var chunk;
                while(null !==(chunk = readable.read())){
                    console.log(`Received ${chunk.length} bytes of data`);
                    }
            });
        readble.setEncoding(encoding);//设置可读流的编码

2.FS的使用介绍

fs.ReadStream[继承自Readable Stream]
    "open" event,当可读流文件被打开的时候
    "close" event,文件描述符被关闭的时候,fs.close()被调用
    readStream.bytesRead,属性,已经读取的字节数
    readStream.path,正在读取流的路径,是fs.createReadStream()的第一个参数,可能是Buffer或者string
fs.WriteStram[继承自WriteStream]
    "open" event,当可写流被打开的时候触发,参数是fd文件描述符
    "close" event,文件描述符被关闭的时候,fs.close()被调用
    writeStream.bytesWritten,已写入的字节数
    writeStream.path,正在写入流的路径,fs.createWriteStream()的第一个参数,可能是Buffer或者string
    
fs.createReadStream(path[,options]);返回一个新建的readStream对象,可读流的highWaterMark是64kb
    options{
        flags:"r",
        encoding:null,
        fd:null,//文件描述符被指定的时候,将会忽略path指定的内容,不会触发"open"事件
        mode:0o666,
        autoClose:true,//如果为false文件描述符不会被关闭,即使发生error
        start:10,//如果不指定,从当前文件位置按照顺序读取
        end:19
        }
fs.createWriteStream(path[,options]);返回一个新建的WriteStream对象
    options{
        flags:"w",//修改一个文件而不是覆盖他,需要使用r+
        defaultEncoding:"utf8",
        fd:null,//同createReadStream
        mode:0o666,
        autoClose:true
        }   
fs.open(path,flags[,mode],callback);异步的打开文件
    callback(err,fd);
    "r"         读,  不存在报错
    "r+"        读写,不存在报错
    "rs+"       同步读写打开文件
    
    "w"         写,  不存在就创建,存在则覆盖
    "w+"        读写,不存在就创建,存在则覆盖
    "wx"        写,  不存在报错
    "wx+"       读写,不存在报错
    
    "a"         追加,不存在就创建
    "ax"        追加,不存在报错
    "a+"        读写追加,不存在就创建
    "ax+"       读写追加,不存在报错
    
    总结,+读写的;x不存在就报错
    
fs.read(fd,buffer,offset,length,position,callback);从fd指定的文件中读取数据到buffer中
    buffer  被写入的buffer
    offset  Buffer中开始写入的偏移量
    length  指定要读取的字节数
    position从文件中开始读取的位置
fs.write(fd,buffer,offset,length[,position],callback);把buffer中的内容写到fd中去
    
fs.readFile(file[,options],callback);异步的读取一个文件的全部内容
    //如果编码未指定,返回的原始buffer
    fs.readFile("/etc/password",(err,data)=>{
        if(err) throw err;
        console.log(data);
        })

fs.writeFile(file,data[,options],callback);异步的写入数据到文件,如果文件已经存在则覆盖
    fs.writeFile("message.txt","hello Node.js","utf8",(err)=>{
        if(err) throw err;
        console.log("It\'s saved!");
        })

3.Buffer使用介绍

Buffer类的实例类似于整数数组,注意:Buffer大小是固定的[不能调整大小],且在V8堆外分配物理内存
    const buf1 = Buffer.alloc(10);//创建一个长度为10,且填充为0的Buffer
    const buf2 = Buffer.alloc(10,1);//创建一个长度为10,且用0x1填充的Buffer
    const buf3 = Buffer.allocUnsafe(10);//创建一个长度为10的Buffer。速度快但是buf种可能有旧数据
    const buf4 = Buffer.from([1,2,3]);//创建一个包含[0x1,0x2,0x3]的Buffer
    const buf5 = Buffer.from("test");//创建一个包含ASCII字节数组[0x74,...]的Buffer
    const buf6 = Buffer.from("test","utf8");//创建一个包含UTF8的字节数组
Buffer支持的编码
    "ascii"
    "utf8"
    "utf16le"
    "ucs2"
    "base64"
    "latin1"
    "binary"
    "hex"
Buffer实例可以通过for ..of语法进行遍历
    const buf = Buffer.from([1,2,3]);
    for(var b of buf){
        console.log(b);//输出1,2,3
        }   
        
Buffer.alloc(size[,fill[,endcoding]]);//size指新建Buffer大小,fill默认填充0,encoding默认编码utf8
    注意:size必须小于buffer.kMaxLength的值,否则将会抛出RangeError错误。如果size小于或等于0,将创建一个长度为0的Buffer
    如果指定了fill,name会自动调用buf.fill(fill);初始化分配的Buffer

Buffer.allocUnsafe(size);//分配一个没有用0填充的Buffer

Buffer.byteLength(string[,encoding]);//string为要计算长度的值,encoding默认为utf8,返回string包含的字节数

Buffer.compare(buf1,buf2);//比较buf1和buf2,通常用于BUffer实例数组的排序
    const buf1 = Buffer.from("1234");
    const buf2 = Buffer.from("0123");
    cosnt arr = [buf1,buf2];
    console.log(arr.sort(Buffer.compare));      //输出[ <Buffer 30 31 32 33>, <Buffer 31 32 33 34> ]
    
Buffer.concat(list[,totalLength]);//返回一个合并后的Buffer
    const buf1 = Buffer.alloc(10);
    const buf2 = Buffer.alloc(14);
    const buf3 = Buffer.alloc(18);
    const totalLength = buf1.length +buf2.length +buf3.length;
    const bufA = Buffer.concat([buf1,buf2,buf3],totalLength);   
    
Buffer.from(array);通过数组创建Buffer

buf[index] 通过索引获取buf中的内容

buf.toJSON();返回buf的JOSN格式,当字符串化一个Buffer实例时,JSON.stringify()会隐式地调用该函数

buf.values();返回一个包含buf值的迭代器,当Buffer使用for..of时会自动调用该函数
    cosnt buf = Buffer.from("buffer");
    for(var value of buf.values()){
        console.log(value);
        }
    for(var value of buf){
        console.log(value);
        }
        
buf.write(string[,offset[,length]][,endcoding]);//把string写入到buf中去,length为写入到buf中的string长度。

4.Events使用介绍

某些对象[触发器]会周期性地触发命名事件来调用函数对象[监听器]
    const EventEmiter  = require("events");
    class MyEmitter extends EventEmiter{};
    const myEmitter = new MyEmitter();
    myEmitter.on("abc",(a,b)=>{
    console.log("发生了一个事件");
    console.log(a,b,this);//这里的this并不是指向EventEmitter的实例,this的打印内容为{}
    });
    myEmitter.emit("abc",10,20);
    
    
    const EventEmiter  = require("events");
    class MyEmitter extends EventEmiter{};
    const myEmitter = new MyEmitter();
    myEmitter.on("abc",function(a,b){
    console.log("发生了一个事件");
    console.log(a,b,this);//这里的this是指向MyEmitter的实例的,打印MyEmitter{...}
    });
    myEmitter.emit("abc",10,20);
    
如果EventEmiter中没有对"error"事件进行监听,如果特殊情况触发"error"将会导致大一堆栈,且退出node.js进程
为了防止上述错误产生,可以在process对象的UNcaughtException事件上注册监听器
    const EventEmiter = require("events");
    class MyEmitter extends EventEmiter{};

    const myEmitter = new MyEmitter();
    process.on("uncaughtException",(err)=>{
        console.log("有错误");
    })
    myEmitter.emit("error",new Error("Whoops!"));//打印有错误
    
最佳的做法还是,始终未error事件注册监听器
    const EventEmiter = require("events");
    class MyEmitter extends EventEmiter{};
    const myEmitter = new MyEmitter();
    myEmitter.on('error', (err) => {
        console.log('有错误');
    });
    myEmitter.emit('error', new Error('whoops!'));// 打印: 有错误

5.有个例子可以看下

var fs = require('fs'),
    path = require('path'),
    out = process.stdout;

var filePath = './a.txt';

var readStream = fs.createReadStream(filePath);
var writeStream = fs.createWriteStream('./b.txt');

var stat = fs.statSync(filePath);

var totalSize = stat.size;
var passedLength = 0;
var lastSize = 0;
var startTime = Date.now();

readStream.on('data', function(chunk) {

    passedLength += chunk.length;

    if (writeStream.write(chunk) === false) {
        readStream.pause();
    }
});

readStream.on('end', function() {
    writeStream.end();
});

writeStream.on('drain', function() {
    readStream.resume();
});

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

推荐阅读更多精彩内容