javascript的一个不足之处是不能处理二进制数据,于是node中引入了Buffer类型。这个类型以一个字节(即8位)为单位,给数据分配存储空间。它的使用类似于Array,但是与Array又有不同:Buffer在定义的时候必须明确知道其长度,但是Array的长度是可以动态变化的。定义Buffer有三种方式:
1. var buf = new Buffer(3);//指定buffer占用3个字节
2. var buf = new Buffer("hello","utf-8");//指定了字符串,以及字符串的编码类型为utf-8,占用5个字节
3. var buf = new Buffer([256,255,2]);//定义了buffer的每个字节所存储的数字,最大为255,超过255取模
为什么介绍Buffer呢?因为本文所介绍的流就是以字节为单位传输的,而Buffer存储的就是字节。
Node中用fs模块的createReadStream和createWriteStream分别创建可读流和可写流。先介绍第一个接口。
一. createReadStream
这个方法继承了EventEmitter类,因而也可以发射和监听相关事件。其用法如下:
var rs = fs.createReadStream(filePath , {options});rs.on("data",function(data){console.log(data);});rs.on("end",function(data){console.log("resource file is completely read");});rs.on("error",function(err){console.log("something is wrong during processing");})
options是一组key-value值,常用的设置如下:
flags: 对文件进行何种操作,默认为‘r‘,读文件
encoding:指定编码,默认为null,如果不设置具体的编码格式,读出的数据就是Buffer类型;也可以使用rs.setEncoding("utf-8")指定编码格式
start:从start开始读取文件
end:读取文件到end为止(包括end)
highWaterMark:最高水位线,内部缓冲区最多能容纳的字节数,如果超过这个大小,就停止读取资源文件,默认值是64KB
前面几个设置就遵从字面意思,比较好理解。重点讲讲highWaterMark。
比如有一个100KB的文件,设置highWaterMark为10KB,那么系统会先从资源文件中读取出10KB的数据,再触发data事件;然后再读取10KB的数据,触发data事件,不断执行,直到读出了所有的数据,触发end事件。highWaterMark不能设置的过小,过小就会频繁的操作文件,影响性能。
可读流还有两个重要的方法:pause和resume,分别可以禁止发射data事件以及激活发射data事件。这两个方法稍后再讲解。
二.createWriteStream
和createReadStream一样,它也是EventEmitter的一个子类。它的drain事件在缓冲区数据写入完毕后被触发。
其用法举例:
var ws = fs.createWriteStream(filePath, {options});
其常用的options值为:
flags:对文件进行何种操作,默认为“w",代表写文件;如果是"a",代表每次写入的时候,不清空文件中的原有数据,而是直接在原有数据的末尾添加新数据
encoding:指定写入的编码格式,默认为null
start:文件开始写入的位置
highWaterMark:缓存区能够容纳的最大字节数,默认为16KB,如果超过这个值,write方法就会返回false
可写流的highWaterMark也代表了缓冲区的容量,如果缓冲区已经装满了,继续写入数据就会失败。只有等缓冲区里的内容被写入文件后,才可以重新调用write方法写入下一个highWaterMark大小的数据(data chunk)。
可写流也有两个重要的方法:write和end,write定义了写入的内容,end可以将还未写入的内容强行写入文件,并且关闭目标文件(不能继续写入了)。
基于以上分析,我们来实现一个文件拷贝的例子:
var fs=require("fs"); var rs = fs.createReadStream("./Koala.jpg");//默认64KB var ws = fs.createWriteStream("./Copy.jpg");//默认16KB,写入速度小于读取速度 rs.on("data",function(data){ var flag = ws.write(data); if (!flag){ //缓冲区已满 rs.pause();//停止触发data事件 } }); ws.on("drain",function(){ rs.resume();//如果当前的缓冲区写入完毕,就重新触发data事件 }); rs.on("end",function(){ ws.end();//将剩下的数据全部写入,并且关闭写入的文件 })
这个例子综合运用了可读流和可写流的事件和重要方法。由于读流比写入流的速度快,所以要控制可读流的数据流动。通过控制data事件的触发时机,就解决了这个问题。