NodeJS 中Stream 的基本使用
在NodeJS中,我们对文件的操作需要依赖核心模块fs,fs中有很基本API可以帮助我们读写占用内存较小的文件,如果是大文件或内存不确定也可以通过open、read、write、close等方法对文件进行操作,但是这样操作文件每一个步骤都要关心,非常繁琐,fs中提供了可读流和可写流,让我们通过流来操作文件,方便我们对文件的读取和写入。
可读流
1、createReadStream创建可读流
createReadStream方法有两个参数,第一个参数是读取文件的路径,第二个参数为options选项,其中有八个参数:
r null null 0o666 true 64*1024
createReadStream的返回值为fs.ReadStream对象,读取文件的数据在不指定encoding时,默认为Buffer。
letfs=require("fs"); //创建可读流,读取1.txt文件 letrs=fs.creatReadStream("1.txt",{ start:0, end:3, highWaterMark:2 });
在创建可读流后默认是不会读取文件内容的,读取文件时,可读流有两种状态,暂停状态和流动状态。
注意:本篇的可写流为流动模式,流动模式中有暂停状态和流动状态,而不是暂停模式,暂停模式是另一种可读流readable。
2、流动状态
流动状态的意思是,一旦开始读取文件,会按照highWaterMark的值一次一次读取,直到读完为止,就像一个打开的水龙头,水不断的流出,直到流干,需要通过监听data事件触发。
假如现在1.txt文件中的内容为0~9十个数字,我们现在创建可读流并用流动状态读取。
letfs=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("读完了"); }); //// //读完了
在上面代码中,返回的rs对象监听了两个事件:
data:每次读取highWaterMark个字节,触发一次data事件,直到读取完成,回调的参数为每次读取的Buffer;
end:当读取完成时触发并执行回调函数。
我们希望最后读到的结果是完整的,所以我们需要把每一次读到的结果在data事件触发时进行拼接,以前我们可能使用下面这种方式。
letfs=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,所以应该用下面这种方式来获取最后读取到的结果。
letfs=require("fs"); letrs=fs.createReadStream("1.txt",{ start:0, end:3, highWaterMark:2 }); //存储每次读取回来的Buffer letbufArr=[]; rs.on("data",data=>{ bufArr.push(data); }); rs.on("end",()=>{ console.log(Buffer.concat(bufArr).toString()); }); //0123
3、暂停状态
在流动状态中,一旦开始读取文件,会不断的触发data事件,直到读完,暂停状态是我们每读取一次就直接暂停,不再继续读取,即不再触发data事件,除非我们主动控制继续读取,就像水龙头打开放水一次后马上关上水龙头,下次使用时再打开。
类似于开关水龙头的动作,也就是暂停和恢复读取的动作,在可读流返回的rs对象上有两个对应的方法,pause和resume。
在下面的场景中我们把创建可读流的结尾位置更改成9,在每次读两个字节并暂停一秒后恢复读取,直到读完0~9十个数字。
letfs=require("fs"); letrs=fs.createReadStream("1.txt",{ start:0, end:9, hithWaterMark:2 }); letbufArr=[]; rs.on("data",data=>{ bufArr.push(data); rs.pause();//暂停读取 console.log("暂停",newDate()); setTimeout(()=>{ rs.resume();//恢复读取 },1000) }); rs.on("end",()=>{ console.log(Buffer.concat(bufArr).toString()); }); //暂停2018-07-03T23:52:52.436Z //暂停2018-07-03T23:52:53.439Z //暂停2018-07-03T23:52:54.440Z //暂停2018-07-03T23:52:55.442Z //暂停2018-07-03T23:52:56.443Z //0123456789
4、错误监听
在通过可读流读取文件时都是异步读取,在异步读取中如果遇到错误也可以通过异步监听到,可读流返回值rs对象可以通过error事件来监听错误,在读取文件出错时触发回调函数,回调函数参数为err,即错误对象。
letfs=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事件用来监听文件的打开,回调函数在打开文件后执行,close事件用来监听文件的关闭,如果创建的可读流的autoClose为true,在自动关闭文件时触发,回调函数在关闭文件后执行。
letfs=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事件。
letfs=require("fs"); letrs=fs.createReadStream("1.txt",{ start:0, end:3, hithWaterMark: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"); }); //open //// //end //close
从上面例子执行的打印结果可以看出只有开始读取文件并读完后,才会关闭文件并触发close事件,end事件的触发要早于close。
可写流
1、createWriteStream创建可写流
createWriteStream方法有两个参数,第一个参数是读取文件的路径,第二个参数为options选项,其中有七个参数:
w utf8 null 0o666 true 16*1024 createWriteStream返回值为fs.WriteStream对象,第一次写入时会真的写入文件中,继续写入,会写入到缓存中。 letfs=require("fs"); //创建可写流,写入2.txt文件 letws=fs.createWriteStream("2.txt",{ start:0, highWaterMark:3 });
2、可写流的write方法
在可写流中将内容写入文件需要使用ws的write方法,参数为写入的内容,返回值是一个布尔值,代表highWaterMark的值是否足够当前的写入,如果足够,返回true,否则返回false,换种说法就是写入内容的长度是否超出了highWaterMark,超出返回false。
letfs=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、可写流的drain事件
drain意为“吸干”,当前写入的内容已经大于等于了highWaterMark,会触发drain事件,当内容全部从缓存写入文件后,会执行回调函数。
letfs=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("吸干"); }); //true //true //false
4、可写流的end方法
end方法传入的参数为最后写入的内容,end会将缓存未写入的内容清空写入文件,并关闭文件。
letfs=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("吸干"); }); ws.end("写完了"); //true //true //false
在调用end方法后,即使再次写入的值超出了highWaterMark也不会再触发drain事件了,此时打开2.txt后发现文件中的内容为"123写完了"。
letfs=require("fs"); letws=fs.createWriteStream("2.txt",{ start:0, highWaterMark:3 }); ws.write("1"); ws.end("写完了"); ws.write("2"); //Error[ERR_STREAM_WRITE_AFTER_END]:writeafterend...
在调用end方法后,不可以再调用write方法写入,否则会报一个很常见的错误writeafterend,文件原有内容会被清空,而且不会被写入新内容。
可写流与可读流混合使用
可写流和可读流一般配合来使用,读来的内容如果超出了可写流的highWaterMark,则调用可读流的pause暂停读取,等待内存中的内容写入文件,未写入的内容小于highWaterMark时,调用可写流的resume恢复读取,创建可写流返回值的rs上的pipe方法是专门用来连接可读流和可写流的,可以将一个文件读来的内容通过流写到另一个文件中。
letfs=require("pipe"); //创建可读流和可写流 letrs=fs.createReadStream("1.txt",{ highWaterMark:3 }); letws=fs.createWriteStream("2.txt",{ highWaterMark:2 }); //将1.txt的内容通过流写入2.txt中 rs.pipe(ws);
通过上面的这种类似于管道的方式,将一个流从一个文件输送到了另一个文件中,而且会根据读流和写流的highWaterMark自由的控制写入的“节奏”,不用担心内存的消耗。
总结
以上所述是小编给大家介绍的NodeJS中Stream的基本使用,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!