2.2 Buffer
Buffer是Node特有(区别于浏览器JavaScript)的数据类型,主要用来处理二进制数据,在前端JavaScript中,和二进制数据打交道的机会比较少(ES2015增加了ArrayBuffer类型,用来操作二进制数据流,Node也可以使用该类型,我们会在下一章介绍)。而Node在进行Web开发时经常需要和前端进行数据通信,二进制数据流十分常见(例如传输一张gif图片),因此Node除了String外,还内置了Buffer这一数据类型,它是Node作为运行时对JavaScript做的扩展。
Buffer属于固有(built-in)类型,因此无须使用require进行引入。
在文件操作和网络操作中,如果不显式声明编码格式,其返回数据的默认类型就是Buffer。例如下面读取文件的例子,如果不指定编码格式,得到的结果就是Buffer字符串。
代码2.2 读取一个文件并打印内容
上面的代码中,最后打印出的是十六进制的数据,由于纯二进制格式太长而且难以阅读,Buffer通常表现为十六进制的字符串。
2.2.1 Buffer的构建与转换
可以使用Buffer类直接初始化一个Buffer对象,参数可以是由二进制数据组成的数组。
如果想由字符串来得到一个Buffer,同样可以调用构造函数来实现,例如:
注意:在最新的Node API中,Buffer()方法被标记为Deprecated,表示已经不推荐使用,因为这个方法在某些情况下可能不安全(参考https://github.com/nodejs/node/issues/4660),并且会在将来的版本中将其移除。
目前推荐的是使用Buffer.from方法来初始化一个Buffer对象,上面的代码可以改写为如下形式。
代码2.3 使用Buffer.from来初始化一个Buffer
如果想把一个Buffer对象转成字符串形式,需要使用toString方法,调用格式为:
Buffer支持的编码类型种类有限,只有以下6种:
- ASCII
- Base64
- Binary
- Hex
- UTF-8
- UTF-16LE/UCS-2
不过也已经覆盖了最常用的编码类型。Buffer还提供了isEncoding方法来判断是否支持转换为目标编码格式。
例如,如果我们想把上一节表示“Hello Node”的Buffer对象转换为字符串,那么可以调用:
如果toString在调用时不包含任何参数,那么就会默认采用UTF-8编码,并转换整个Buffer对象。
2.2.2 Buffer的拼接
在中国古代,有一种诗被称作回文诗,我们先来看一个例子,如图2-1所示。
图2-1
这首诗是北宋秦观所作,如果不清楚如何断句,就无法得到正确的诗,正确的断句:
赏花归去马如飞
去马如飞酒力微
酒力微醒时已暮
醒时已暮赏花归
Buffer一个常见的使用场景是用来处理HTTP的post请求,随便在网络上搜索,都能看到类似如下的代码。
代码2.4 使用+=来拼接Buffer
上面的代码使用+=来拼接上传的数据流,这个过程包含了一个隐式的编码转换。
body+=chunk相当于body+= chunk.toString(),当上传字符全都是英文的时候固然没关系,但如果字符串中包含中文或者其他语言,由于toString方法默认使用utf-8编码,这时就有可能出现乱码,就像回文诗不按照格式断句,只会得到几个不通顺的句子一样。
举个例子,我们先构造一个中文的字符串,并将其另存为test.txt。
然后我们写一段代码来尝试一下:
highWaterMark
正如其字面意思最高水位线,它表示内部缓冲区最多能容纳的字节数,如果超过这个大小,就停止读取资源文件,默认值是64KB。
假设文件大小为100KB,那么在默认情况下,系统就会每次从文件里读取64KB大小的数据,随后触发data事件;chunk的大小即为highWaterMark的大小;然后接着读取36KB大小的文件,再次触发data事件;随后文件读取结束,触发end事件。
如果highWaterMark设置得很小,那么就会发生多次系统调用,这会对性能造成影响。
由于我们要读取的目标文件很短,因此只设置了10个字节位highWaterMark。
试着运行上面的代码,得到下面的输出:
可以看到输出中产生了乱码,我们知道utf-8中一个汉字占三个字节,那么我们将highwatermark设置为10后,每三个字之后都会有一个字被截断,因此在调用toString方法的时候出现了乱码。读者也可以将每个chunk的内容打印出来看看,这里不再介绍(朴灵写的《深入浅出Node.js》一书中用一个章节讨论这个问题,建议读者阅读一下)。
目前上面的代码已经被舍弃,官方的推荐做法是使用push方法来拼接Buffer,上面的代码可改写成下面形式:
代码2.5 使用数组来拼接Buffer
上面的代码在拼接过程中不会有隐式的编码转换,首先将Buffer放到数组里面,等待传输完成后再进行转换,这样就不会出现乱码了。