NodeJS之Buffer

  Node       2017-01-14

本文从以下几个方面去梳理NodeJS中关于Buffer的内容 [文章中所涉及到的API为NodeJSv7.4.0版本] :

  • 为什么要引入Buffer?
  • Buffer是什么?
  • 如何使用?
  • 性能问题;
  • 底层知识;

为什么要引入Buffer?

在计算机中,编码的概念由来已久,是用于将计算机存储的内容以大家可以看懂的形式进行展示。不同的编码,用不同的规则解释计算机存储的二进制数据,比如:英文字符可以用一个字节表示,一个汉字可能需要三个字节。

JavaScript为浏览器而设计,能够很好的处理Unicode编码的字符串,但对于二进制或非Unicode编码的数据显得无能无力。在NodeJS中,对于需要处理的网络数据、文件内容等,都是以二进制格式存在。因此,NodeJS扩展了JavaScript语言,为二进制数据的处理提供了Buffer类。

Buffer是什么?

Buffer是一个类似Array的对象,主要用于操作字节。元素为16进制的两位数,即0~255的值。

  1. const str = "深入浅出node.js";
  2. const buf = Buffer.from(str, 'utf8');
  3. console.log(buf);
  4. // UTF-8编码下,中文占3个元素;
  5. // <Buffer e6 b7 b1 e5 85 a5 e6 b5 85 e5 87 ba 6e 6f 64 65 2e 6a 73>

Buffer常驻内存,挂载在全局对象[global]上;

如何使用?

构建Buffer对象

  • Buffer.alloc(length);形式,用0填充;
  • Buffer.from(str, [encoding]);形式;

length属性

  • 可以通过length得到长度;
  • 元素可以通过下标访问,也可以通过下标赋值[每个元素值在0~255之间];

Buffer转换

Buffer可以和字符串相互转换,是否支持字符串编码可通过Buffer.isEncoding(encoding)进行判断;

字符串转Buffer

  1. // 只能一种编码类型,默认UTF-8;
  2. Buffer.from(string, [encoding]);
  3. // 存储不同编码类型的字符串转码的值;
  4. buf.write(string, [offset], [length], [encoding]);

Buffer转字符串

  1. // 编码默认UTF-8;
  2. buf.toString([encoding], [start], [end]);

对于不支持的编码类型,可以通过相关模块来进行实现,如iconviconv-lite;例如:

  1. const iconv = require('iconv-lite');
  2. // Buffer转字符串
  3. const str = iconv.decode(buf, 'win1251');
  4. // 字符串转Buffer
  5. const buf = iconv.encode('String', 'win1251');

Buffer的拼接

  1. // 错误示例:
  2. rs.on('data', (trunk) => {
  3. // data = data.toString() + trunk.toString();
  4. // 如果有宽字符,可能会出现乱码;
  5. data += trunk;
  6. });
  7. // 恰当的做法如下:
  8. // 把输入流中的Buffer存储到List中,利用concat()拼接;
  9. // 这样处理,也有助于提供性能;
  10. Buffer.concat(List, size):
  11. // 问题:如果数据流非常大,大到内存爆,怎么办?
  12. // 答:单个NodeJS进程是有最大内存限制的,Buffer对象占用的内存空间是不计算在NodeJS进程内存空间限制上的,因此,可以用Buffer存储需要占用较大内存的数据,最大值与Buffer.kMaxLength有关;
  13. const buf = Buffer.alloc(1024 * 1024 * 1024 - 1);

可以通过bufferhelper简化地处理Buffer对象;

乱码问题

乱码的原因归根结底就是:Buffer没有被按照正确的编码类型来进行转换,导致字符显示乱码;

没有被正确的编码,可能会有以下几种情况:

  • 字节不足,无法编码;
  • 编码类型不同;
  • 不支持或不知道数据流中的编码类型;

解决乱码的方式:

  • 数据流读取完成后,拼接;[见上面Buffer的拼接示例]
  • 指定转换时的编码类型,如rs.setEncoding(‘utf8’),该方法在data事件中传递的不再是Buffer对象,而是编码中的字符串,通过string_decoder;再比如iconv.decode(buf, ‘utf8’)方法;

性能

Buffer数据传输

在网络中传输的数据,需要转换为Buffer,以二进制数据传输;提高字符串到Buffer的转换效率,可以提高网络吞吐量。

在《深入浅出NodeJS》作者的实验中,通过预先转换静态内容为Buffer对象,QPS提升近一倍。因此,对于静态内容,它以二进制数据存在,在传输时预先转换为Buffer类型即可。

在文件读取时,通过设置highWaterMark调节每次读取的内容大小,来提高处理速度。理论上,处理大文件时,highWaterMark越大,处理速度越快。

字符串拼接 对比 Buffer拼接

Node.js缓冲模块Buffer | 粉丝日志 的实验中,对于字符串连接,循环30万次,String对字符串的连接操作,要远快于Buffer的连接操作。

因此,我们在保存字符串的时候,该用string还是要用string;只有在保存非UTF-8的字符串以及二进制数据的情况下,我们才用Buffer。

数据存储

对于一个时间戳如1447656645380,我们应该怎么样存储呢?如果当做字符串,需要占用11个字节;而我们把它转换为二进制数据,仅需6个字节。

  1. const buf = Buffer.allocUnsafe(6);
  2. buf.writeUIntBE(1447656645380, 0, 6); // <Buffer 01 51 0f 0f 63 04>
  3. buf.readUIntBE(0, 6); // 1447656645380

所以,请留意Buffer文档中的readXXX()writeXXX()方法。

底层知识

为了更好的了解的Buffer,您还需要了解的有:

  • NodeJS中Buffer的实现;
  • Buffer的内存分配策略;[可了解8KB界限]

防止误人子弟,本文中暂不涉及。

参考链接

本文最后更新于2017-01-15 00:02:42