阅读原文读取一行后触发监听器的事件,对这一行数据进行处理。创建一个LineReader类实现“行读取器”的总体思路是创建一个类的实例,然后在这个实例上监听一个事件,开始读取文件,每读取一行就触发.这里我们把这个类命名为LineReader,因为这个类需要监听事件,需要继承EventEmitter。//LineReaderLineReader类//导入依赖项constEventEmitter=require("events");constfs=require("fs");//linereader的类,参数为读取文件的路径classLineReaderextendsEventEmitter{contructor(path){super();this.path=路径;//文件路径this._rs=fs.createReadStream(this.path);//创建一个可读流this.current=null;//存储每次读取的单个字节this.arr=[];//一个数组,为文件的每一行存储一个字节Bufferthis.system=null;//默认系统(windows或mac)this.RETURN=13;//\r的十六进制数this.Line=10;//\n的十六进制数//监听newListenerthis.on("newListener",readLineCallback.bind(this));}}在LineReader实例上定义了system(当前系统)、current(每次读取一个字节)、RETURN(\r十六进制编码)和Line(\n十六进制编码)等属性,以备后用。我们要在监听的事件触发之前执行读取一行文件的逻辑,也就是说我们需要一个监听事件时可以执行的函数,所以我们需要在创建实例之前监听newListener事件,并设置newListener的回调作为这个函数来执行,顺便获取参数中的事件类型。我们把读取文件的核心逻辑放在newListener事件的回调函数中,并将这个回调函数命名为readLineCallback。为了保证readLineCallback内部使用的this在执行过程中是LineReader的一个实例,使用bind来修正。linereader的核心逻辑readLineCall函数,如果有需要默认开始读取,每读取一个字节就可以进行下一次循环读取。这种场景最适合可读流的暂停模式。可读事件默认触发一次,“容器”读取一个字节,它会自动“重新填充”特征。//linereaderfunctionreadLineCallback(type)的核心逻辑{//使用暂停模式读取this.on("readable",()=>{if(type==="newLine"){//ForCompared使用\r和\n,一次只读取一个字节while((this.current=this._rs.read(1))){//结果是Buffer,所以使用索引取值比较switch(this.current[0]){caseRETURN://forWindowsthis.system="windows";this.disposeLine();//处理换行逻辑break;caseLINE://forMacthis.system="mac";this.disposeLine();//处理换行符逻辑中断;默认://每个读取换行符的字符都存储在数组中this.arr.push(current);}}}});//防止最后一行丢失this.on("end",this.disposeLine.bind(this));}上面代码中,监听可读事件并验证事件类型是否为newLine,然后读取循环中的文件内容。为了与换行符的十六进制代码进行比较,一次只读取一个字节,当遇到换行符时,确认当前系统,调用换行符处理函数disposeLine进行处理注意:文件的最后一行上次可能没有换行符,所以不满足switch中语句的条件,即disposeLine为不用于处理,所以监听可读流的end事件,让disposeLine在end触发时作为回调函数执行,注意使用bind将this修改为当前实例。兼容Windows和Mac的换行符处理功能在换行符处理功能上,Windows与其他系统(Mac、Linux)唯一的区别是Window系统的换行符是\r\n,比\Mac和Linux的n一个字节,读取下一行时,这个字节没有用,需要忽略。//换行处理functionLineReader.prototype.disposeLine=function(){//发出这一行的内容并清空数组this.emit("newLine",Buffer.concat(this.arr).toString());这个.arr=[];//如果是window系统,下一个是\n,就多读一个字节,不存入组if(this.system==="windows"){this._rs.read(1);}};验证LineReaderlinereader创建一个“LineReader”需要创建一个LineReader类的实例并传入要读取的文件的路径,因为newListener的回调是在源码函数中执行的,所以只需要添加newLine事件监听器,然后在默认触发readable时在内部循环读取,并在发送之前重新整合每一行读取的内容,从而实现newLine事件的持续触发,直到文件被阅读。//使用行阅读器//创建文件1.txt每次内容为1~99个数字,每3个字符为一行letlineReader=newLineReader("1.txt");lineReader.on("newLine",data=>{console.log(`-------${data}------`);});//------123------//------456------//------789------"LineReader"lineReader读取到每一行数据的处理逻辑主要在newLine的回调函数中事件,如上例,在每行前后添加------并打印。总结在NodeJS中,流被广泛使用,“行阅读器”只是其中之一。根据流的不同模式的不同特性,可以实现更复杂的功能,所以流在NodeJS中还是很重要的。
