摘要:在学习Node的过程中,Stream流是常用的东东,在了解怎么使用它的同时,我们应该要深入了解它的具体实现。今天的主要带大家来写一写可读流的具体实现,就过来,就过来,上码啦!

码前准备

在写代码之前我们首先要整理下思路,我们要做什么,以及怎么来做。本篇文章以文件可读流为例,一个可读流大体分为四步:

  1. 初始化参数
  2. 打开文件
  3. 读取文件
  4. 结束,关闭文件

一、先来一波调用

  • 1.先引入一个readStream模块
  • 2.实例化并传入参数
var readStream=require('readStream.js');
var rs=new readStream('test.txt',{
flags:'r', //打开文件的模式
autoClose:true, //结束是否自动关闭
encoding:'utf8', //字符编码
highWaterMark:3, //每次读取的字节数
start:0, //从下标为多少的位置开始读取,默认以0开始
end:3, //结束下标位置
});
  • 3.监听data事件,接收读到的值

    对于有读取数据时,会触发data事件,我们在此先监听data事件。关于事件的监听和触发,在node中用的是‘events’模块,如果不太了解的盆友,可以关注我哈,后续的文章会介绍到哦!本篇的重点是流,我们就先直接用了。
rs.on('data',function(data){
console.log(data);
})

二、接下来定义readStream这个模块

1.因为我们以文件的可读流来做的,在此我们要引入一个文件模块。还有一个事件模块,并且要继承它,每一个可读流都是‘events’的一个实例。

首先我们先初始化参数:

    var fs=require('fs');
var EventEmitter=require('events');
class readStream extends EventEmitter{
constructor(path,options){
super();
this.path=path;
this.flags=opitons.flags||'r';
this.autoClose=opitons.autoClose||true;
this.encoding=options.encoding||null;
this.highWaterMark=options.highWaterMark||64*1024;
this.start=options.start||0;
this.end=options.end; this.pos=this.start;
this.buffer=Buffer.alloc(this.highWaterMark);
this.flowing=null;
this.open();
}
}

以上除了初始化传递进来的参数,还加了几个pos,buffer,open(),flowing,为什么要加这些呢?这些值是来做什么用的?我们在此做出解答:

  • pos:是用在存储每一次读取文件时,读取的位置。比如当highWater<end时,data有可能会触发多次,每次的位置应该是上次读取位置的下一个,pos就是用来记录这个位置的下标,所以初始值为start。
  • buffer:分配一个长度为this.highWaterMark的Buffer。
  • flowing:是指当前状态是否是流动的,有三个值,初始为null。当开始监听data事件时,值为true,则开始读取文件。当值为false时,暂停读取文件。为什么刚刚我说data可能会多次触发,因为当flowing被设为false时,data事件将停止触发。想要改变flowing的值,node提供了两个方法暂停pause()和恢复resume()。

2.读取一个文件应该先打开文件,我们来定义该方法:

    open(){
fs.open(this.path,this.flags,(err,fd)=>{
if(err){
this.emit('err');
}
this.fd=fd;
this.emit('open');
});
}

2.1在打开文件的时候,如果文件打开报错,我们除了要触发错误事件外,还要注意一个参数。autoClose是指在文件读取完毕或抛出错误后,自己关闭文件。

于是我们根据这个参数值,在现有的open方法中对抛错的情况做出优化。

    open(){
fs.open(this.path,this.flags,(err,fd)=>{
if(err){
if(autoClose){
if(typeof this.fd === 'number'){
fs.close(this.fd,()=>{
this.emit('close');
});
}
this.emit('close');
}
this.emit('err');
}
this.fd=fd;
this.emit('open');
})
}

3.打开文件后,并不是立马读取,而是要检查是否有data事件绑定监听!

对此,我们要在构造函数内检查如果添加了data的事件监听

    class readStream extends EventEmitter{
constructor(path,options){
super();
...
this.on('newListener',(eventName,callback)=>{
if(eventName=='data'){
this.flowing=true;
this.read();
}
})
}
}

完成以上步骤后,我们要做的就是读取文件内容啦,下面来自定义一个read方法:

  • 先判断是否有读取到内容,若有读取到内容
  • --改变下次读取的起点位置
  • --获取到相同长度的Buffer空间
  • --若设了字符编码,要将data作相应的转换
  • --判断此时this.pos的位置是否已超出了结束位置
  • --如果folwing为true,则再次调用read方法
  • 读取不到内容则抛出一个错误,并关闭文件

    代码如下
    read(){
let howToLength=this.end ? Math.min((this.end-this.pos),this.highWaterMark) : this.highWaterMark;
fs.read(this.fd,this.buffer,0,howToLength,this.pos,(err,bytesBase)=>{
if(bytesBase>0){
this.pos+=bytesBase;
this.buf=this.buffer.slice(0,bytesBase);
let data=this.encoding ? this.buffer.toString(this.encoding) : this.buffer.toString();
this.emit('data',data); if(this.end>this.pos){
this.emit('end');
if(autoClose){
if(typeof this.fd === 'number'){
fs.close(this.fd,()=>{
this.emit('close');
});
}
this.emit('close');
}
}
if(flowing){
this.read();
}
}else{
this.emit('err');
if(typeof this.fd === 'number'){
if(autoClose){
fs.close(this.fd,()=>{
this.emit('close');
});
}
this.emit('close');
}
}
})
}

到此,一个read方法就写的差不多了,但是有个问题是要注意的,open方法是异步的,有可能出现调用read方法时,this.fd还没有值。为了避免这个错误,我们改写一下read方法。

    read(){
if(typeof this.fd !== 'number'){
this.once('open',()=>this.read());
}
...
}

这样的话,一个基础的readStream类才算写完整。我们是不是要考虑下,有没有什么可以优化的地方?细心的伙伴是不是发现有重复的代码?

对,就是文件的关闭,我们提出一个destory方法,用作关闭文件。

    destory(){
if(typeof this.fd==='number'){
if(autoClose){
fs.close(this.fd,()=>{
this.emit('close');
});
return ;
}
this.emit('close');
}
}

三、扩展

方法的调用介绍变量flowing时,我们有提到'暂停'方法pause(),'重启'方法resume()来改变flowing的值。我们加入到代码中。

  1. 首先加入调用,我们在第一次读取数据后暂停读取,在3秒后继续读取。
    rs.on('data',(data)=>{
console.log(data);
this.pause();
});
setTimeout(()=>{
this.resume();
},3000)
  1. 这两个方法的调用也是一样简单:
    pause(){
this.flowing=false;
}
resume(){
this.flowing=true;
this.read();
}

OK,大功告成了,下面整理出完整代码

var fs=require('fs');
var EventEmitter=require('events');
class readStream extends EventEmitter{
constructor(path,options){
super();
this.path=path;
this.flages=options.flages||'r';
this.autoClose=options.autoClose||true;
this.encoding=options.encoding||null;
this.highWaterMark=options.highWaterMark||64*1024;
this.end=options.end;
this.start=opitons.start||0; this.pos=this.start;
this.flowing=false;
this.buffer=Buffer.alloc(this.highWaterMark);
this.open(); this.on('newListener',(eventName,callback){
if(eventName=='data'){
this.flowing=true;
fs.read();
}
});
open(){
fs.open(this.path,this.flags,(err,fd){
if(err){
if(this.autoClose){
this.destory();
}
this.emit('err',err);
return ;
}
this.fd=fd;
this.emit('open'); });
}
destory(){
if(typeof this.fd ='number'){
fs.close(this.fd,()=>{
this.emit('close');
});
return ;
}
this.emit('close');
}
read(){
if(typeof this.fd !== 'number'){
return this.once('open',()=>this.read());
}
let howToLength=this.end ? Math.min((this.end-this.pos),this.highWaterMark) : this.highWaterMark;
fs.read(this.fd,this.buffer,0,howToLenghth,this.pos,(err,bytesBase)=>{
if(bytesBase>0){
this.pos+=bytesBase;
let buf=this.buffer.slice(0,bytesBase);
let data=this.encoding ? this.buffer.toString(this.encoding) : this.buffer.toString();
this.emit('data',data); if(this.pos>this.end){
this.emit('end');
this.destory();
}
if(flowing){
this.read()
}
}else{
this.emit('err');
this.destory();
}
})
}
pause(){
this.flowing=false;
}
resume(){
this.flowing=true;
this.read();
}
}
}

确认过眼神,你是喜欢Stream的人的更多相关文章

  1. python爬取链家二手房信息,确认过眼神我是买不起的人

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资 ...

  2. i春秋四周年中奖名单出炉丨确认过眼神,你是中奖人

    i春秋四周年任性狂欢倒计时最后2天! 优享会员.精品课程.CTF经典赛题实战班.Web安全线上提高班.渗透测试工程师线下就业班.CISAW-Web安全认证......全部史上最低折扣,还有8888元现 ...

  3. 【悬赏征文】确认过眼神,你就是腾讯WeTest的特约撰稿人

    成功的经验从来不是一蹴而就的,它仰仗基于价值认同的信息互通,知识共享. 在开发.测试领域"摸爬滚打"多年的你,一定有很多经验与见解分享. 如何让更多同行了解自己的独到见解? 怎样才 ...

  4. MTSC2018 | 确认过眼神,在这里能遇见Google、阿里、百度......

    MTSC2018部分Topic曝光啦 Google,阿里,百度,美团,小米,360,网易等公司是如何将技术转化为现实生产力,提高工作效率的?离开Saucelab的Jonathan又是如何规划Appiu ...

  5. 确认过眼神,看清HTTP协议

    导读:什么是 HTTP?它有什么属性?我们常用的是什么呢?快来阅读本文,将会为你一一道来. 什么是 HTTP 协议? 在了解HTTP之前,我们需要了解什么是网络通信模型(也就是我们常说的 OSI 模型 ...

  6. 确认下眼神!有没有遇上对的工资(测试leader)

    屏蔽敏感信息,直接上图: ▼

  7. 借书证信息管理系统,C语言实现

    自己实现的如有缺漏欢迎提出 /* 原创文章 转载请附上原链接: https://www.cnblogs.com/jiujue/p/10325628.html   */ 设计内容: 设计一个排序和查找系 ...

  8. 从零开始的程序逆向之路基础篇 第二章——用OllyDbg(OD)分析一个简单的软件

    作者:Crazyman_Army 原文来自:https://bbs.ichunqiu.com/thread-43469-1-1.html 0x00知识回顾 (由于笔者省事,没开XP虚拟机,而且没关闭A ...

  9. asp.net core使用水晶报表问题

    背景     最近项目上遇到一个需求,要后台通过定时任务把水晶报表生成pdf文件,然后邮件发送给相关人. 技术实现思路     选用ASP.NET Core框架(基于2.2版本),通过IHostedS ...

随机推荐

  1. KVM之七:KVM克隆

    1.在克隆虚拟机之前,必须先暂停或者停掉kvm 虚拟机.以虚拟机 snale 为例,先暂停虚拟机,如下 [root@kvm ~ ::]#virsh list Id 名称 状态 ------------ ...

  2. 爬虫(scrapy中的ImagesPipeline)

    在使用ImagesPipeline对妹子图网站图片进行下载时,遇到302错误,页面被强制跳转. 解决办法如下: # -*- coding: utf-8 -*- # Define your item p ...

  3. Alpha冲刺No.3

    冲刺Day3 一.站立式会议 终于我们遇到了我们最艰难的时候,组员也反映每天做的事情越来越少,出现了问题越来越多. 人太少,时间太少,我们没有办法一个人花足够多的时间去钻研统一个问题,或许是所以组员的 ...

  4. 团队开发---”我爱淘“校园二手书店 NABC分析

    本项目特点之一:可预订 N:对于一些抢手的书可以提前预定,避免学生买不到书 A:网上下单,通过手机便捷购物 B:使得订书更加方便快捷 C:二手书摊.网上书店 团队成员:杨广鑫.郭健豪.李明.郑涛

  5. Flask 视图

    写个验证用户登录的装饰器:在调用函数前,先检查session里有没有用户 from functools import wraps from flask import session, abort de ...

  6. fread函数详解

    函数原型: size_t   fread(   void   *buffer,   size_t   size,   size_t   count,   FILE   *stream   )    b ...

  7. 十、Python练习----基础搭建飞机大战

    只是简单的学习了pygame,实现飞机的摧毁还需要多张图片的切换,和sprite(碰撞精灵),还有多种音效的添加(如背景音乐.摧毁特效).以后再深入学习我只是练习一下python. 一.搭建界面(基于 ...

  8. easyUI combobox 添加空白项

    今天测试反馈了一个问题,希望可以在下拉框下面加一个空白的选项(下拉框用的是combobox方法). 开始分析这个问题: 首先,这个数据都是后台读出来的,那么我在后台直接添加可以么,答案是可以的,如果没 ...

  9. OpenCASCADE Trihedron Law

    OpenCASCADE Trihedron Law eryar@163.com Abstract. In differential geometry the Frenet-Serret formula ...

  10. vue 内联样式style中的background

    在我们使用vue开发的时候   有很多时候我们需要用到背景图 这个时候会直接使用 内联样式 直接把你拿到的数据拼接上去 注意  在vue中直接使用style时 花括号一定别忘记 还有就是你的url一定 ...