阅读原始流的介绍在NodeJS中,我们需要依赖核心模块fs来进行文件操作。fs中有一个非常基础的API可以帮助我们读写占用内存较少的文件。大文件或者不确定内存也可以通过open、read、write、close等方法对文件进行操作,但是操作文件的每一步都必须关注,非常繁琐。fs中提供了可读流和可写流,这样我们就可以通过流来操作文件,方便我们读写文件。可读流1.createReadStream创建一个可读流。createReadStream方法有两个参数。第一个参数是读取文件的路径,第二个参数是options选项。一共有八个参数:flags:标识位,默认为r;encoding:字符编码,默认为null;fd:文件描述符,默认为null;mode:权限位,默认为0o666;autoClose:是否自动关闭文件,默认为true;start:读取文件的起始位置;end:读取文件的(含)结束位置;highWaterMark:读取文件的最大字节数,默认64*1024。createReadStream的返回值是一个fs.ReadStream对象,不指定编码时从文件读取的数据默认为Buffer。constfs=require("fs");//创建可读流,读取1.txt文件letrs=fs.creatReadStream("1.txt",{start:0,end:3,highWaterMark:2});创建可读流后,默认不会读取文件内容。在读取文件时,可读流有两种状态,暂停状态和流动状态。注意:本文中的可写流是流模式。流模式有暂停状态和流状态,暂停模式没有。暂停模式是另一种可读流可读。2.流态流态是指文件一旦被读取,就会根据highWaterMark的值一次读取一个,直到读取完毕,就像打开的水龙头一样,水会不断流出,直到干涸起来,需要监控。数据事件触发。假设1.txt文件中的内容是0到9的十个数字,我们现在创建一个可读流,以流动的状态读取。//流状态constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:3,highWaterMark:2});//读取文件rs.on("data",data=>{console.log(data);});//监听读取结束rs.on("end",()=>{console.log("readingfinished");});//////读取后上面代码中,返回的rs对象监听两个事件:data:每次读取highWaterMark字节,触发一个data事件,直到读取完成,回调的参数为每次读取的Buffer;end:读取完成时触发并执行回调函数。我们希望最后一次读取的结果是完整的,所以需要在数据事件触发的时候拼接每一次读取的结果。以往,我们可能会使用下面的方法。//错误的数据拼接方式constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:3,highWaterMark:2});letstr="";rs.on("data",data=>{str+=data;});rs.on("end",()=>{console.log(str);});//上面代码中的0123如果读取的文件内容是中文,每次读取的highWaterMark是两个字节,不能组成一个完整的汉字。+=操作每次读取时默认会调用toString方法,这会导致最后读取的结果是乱码。以后通过流操作文件的时候,大多数情况下操作的是Buffer,所以应该使用下面的方法获取最后一次读取的结果。//正确拼接数据的方式constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:3,highWaterMark:2});//存储每一个time回读BufferletbufArr=[];rs.on("data",data=>{bufArr.push(data);});rs.on("end",()=>{console.log(Buffer.concat(bufArr).toString());});//01233.暂停状态处于流状态。一旦开始读取文件,就会不断触发数据事件,直到读取完成。暂停状态就是每次我们读取的时候直接挂起,不继续读取,即不再触发数据事件,除非我们主动控制继续读取,就像打开水龙头一次,然后马上关掉水龙头一样,然后在下次打开它。类似于打开和关闭水龙头的动作,即暂停和恢复阅读的动作,在可读流返回的rs对象上有两个对应的方法,pause和resume。在下面的场景中,我们将创建的可读流的结束位置改为9,每次读取两个字节后暂停一秒继续读取,直到读取完0到9的十个数字。//暂停状态constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:9,highWaterMark:2});letbufArr=[];rs.on("data",data=>{bufArr.push(data);rs.pause();//暂停读取console.log("pause",newDate());setTimeout(()=>{rs.resume();//恢复阅读},1000)});rs.on("end",()=>{console.log(Buffer.concat(bufArr).toString());});//pause2018-07-03T23:52:52.436Z//暂停2018-07-03T23:52:53.439Z//暂停2018-07-03T23:52:54.440Z//暂停2018-07-03T23:52:55.442Z//pause2018-07-03T23:52:56.443Z//01234567894,通过可读流读取文件时,错误监听是异步读取,如果异步读取时遇到错误,也可以通过异步监听检测到,可读流返回valuers对象可以通过error事件监听错误,当读取文件出错时触发回调函数。回调函数的参数为??err,即错误对象。//监听错误constfs=require("fs");//读取一个不存在的文件letrs=fs.createReadStream("xxx.js",{highWarterMark:2});letbufArr=[];rs.on("data",data=>{bufArr.push(data);});rs.on("err",err=>{console.log(err);});rs.on("end",()=>{console.log(Buffer.concat(bufArr).toString());});//{Error:ENOENT:nosuchfileordirectory,open'......xxx.js'......}5.打开和关闭文件的监听流具有广泛的适用性,不仅可以读写文件,还可以用于http中的数据请求和响应,但是对于读取文件返回的rs有两个专有事件用于监听打开和关闭文件。open事件用于监听文件的打开。回调函数在文件打开后执行。关闭事件用于监听文件的关闭。如果创建的可读流的autoClose为true,则在文件自动关闭时触发。回调函数在文件关闭后执行。//打开和关闭可读流的监听器constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:3,highWaterMark:2});rs。on("open",()=>{console.log("open");});rs.on("close",()=>{console.log("close");});//open在上面的代码中,我们可以看到只要创建了一个可读流,就会打开文件,并触发open事件。因为默认暂停,不读取文件,所以不会关闭文件,也就是不会触发close事件。//暂停状态constfs=require("fs");letrs=fs.createReadStream("1.txt",{start:0,end:3,highWaterMark:2});rs.on("open",()=>{console.log("open");});rs.on("data",data=>{console.log(data);});rs.on("end",()=>{console.log("end");});rs.on("close",()=>{console.log("close");});//打开//////end//close从上面例子的打印结果可以看出,文件会被关闭,只有文件被关闭后才会触发close事件阅读并完成。结束事件的触发早于关闭。可写流1.createWriteStream创建一个可写流。createWriteStream方法有两个参数。第一个参数是读取文件的路径,第二个参数是options选项。共有七个参数:flags:标识位,默认为w;encoding:字符编码,默认为utf8;fd:文件描述符,默认为null;mode:权限位,默认为0o666;autoClose:是否自动关闭文件,默认为true;start:写入文件的起始位置;highWaterMark:比较写入字节数的标志,默认为16*1024。createWriteStream的返回值是一个fs.WriteStream对象,第一次写入时会真正写入文件,并且会如果继续写入,则写入缓存。//创建可写流constfs=require("fs");//创建可写流并写入2.txt文件letws=fs.createWriteStream("2.txt",{start:0,highWaterMark:3});2.可写流的write方法将内容写入可写流中的文件,需要用到ws的write方法,参数是要写入的内容,返回值是一个布尔值,代表是否highWaterMark的值是否足够当前Write,如果足够则返回true,否则返回false,即写入内容的长度是否超过highWaterMark,超过则返回false。//write方法写入constfs=require("fs");letws=fs.createWriteSteam("2.txt",{start:0,highWaterMark:3});letflag1=ws.write("1");console.log(flag1);letflag2=ws.write("2");console.log(flag2);letflag3=ws.write("3");console.log(flag3);//true//true//false写入不存在的文件时,会自动创建文件。如果start的值不为0,写入不存在的文件时默认不会找到写入位置。3、writablestreamdrain的drain事件,意思是“drain”。当前写入的内容已经大于等于highWaterMark,会触发drain事件。当所有内容从缓存写入文件后,回调函数就会执行。//draineventconstfs=require("fs");letws=fs.createWriteStream("2.txt",{start:0,highWaterMark:3});letflag1=ws.write("1");console.log(flag1);letflag2=ws.write("2");console.log(flag2);letflag3=ws.write("3");console.log(flag3);ws.on("drain",()=>{console.log("drain");});//true//true//false4、可写流的end方法end方法传入的参数为最后写入的内容,end会将缓存中未写入的内容清空并写入文件,并关闭文件。//结束方法constfs=require("fs");letws=fs.createWriteStream("2.txt",{start:0,highWaterMark:3});letflag1=ws.write("1");console.log(flag1);letflag2=ws.write("2");console.log(flag2);letflag3=ws.write("3");console.log(flag3);ws.on("drain",()=>{console.log("drain");});ws.end("finished");//true//true//false调用结束方法后,即使再写一遍如果该值超过highWaterMark,则不会再次触发drain事件。这时打开2.txt后发现文件中的内容是“123写完”。//常见错误constfs=require("fs");letws=fs.createWriteStream("2.txt",{start:0,highWaterMark:3});ws.write("1");ws.end("finished");ws.write("2");//错误[ERR_STREAM_WRITE_AFTER_END]:writeafterend...调用end方法后,不能调用write方法写入,否则会报很常见的错误end后写入的错误,会清空文件原来的内容,不会再写入新的内容。可写流和可读流的混合使用可写流和可读流一般是一起使用的。如果读取的内容超过可写流的highWaterMark,调用可读流的pause暂停读取,等待内存中的内容写入文件,当未写入的内容小于highWaterMark时,调用可写流的resume恢复阅读。rs上创建可写流返回值的pipe方法专门用于连接可读流和可写流。将从一个文件读取的内容通过stream写到另一个文件中。//pipe方法使用constfs=require("fs");//创建可读可写流letrs=fs.createReadStream("1.txt",{highWaterMark:3});让ws=fs。createWriteStream("2.txt",{highWaterMark:2});//通过流将1.txt的内容写入2.txtrs.pipe(ws);通过上面类似流水线的方式,将A流从一个文件发送到另一个文件,写入的“节奏”可以根据读流和写流的highWaterMark自由控制,不用担心内存消耗。综上所述,这篇文章讲的是读写流的基本用法。在平时的开发中,绝大部分的API都不会用到,只有最后一个pipe用的最多,无论是在文件的读写还是请求的响应,其他的API虽然很少用到,但是一定要理解为一个合格的程序员。