极简 Node.js 入门系列教程 —— 4.1 stream 学习第一步:Buffer

246 阅读5分钟

计算机就是处理 0 和 1,很尴尬的是在引入 TypedArray 之前,JavaScript 没有操作二进制数据流的机制,Buffer 类用一种更适合 Node.js 的方式实现了 Uint8Array API,用于在 TCP 流、文件系统操作等场景处理二进制字节

bit 与 Byte

  1. bit 是我们常说的比特,比特币就是以此命名的,bit 是二进制的最小信息单位,1 bit 就是我们说的 1位,64 位操作系统 CPU一次能处理  位的数据
  2. Byte 被翻译为字节,是计量存储或者传输流量的单位,硬盘容量、网速等说的都是字节,一个英文字符是一个字节,也就是我们说的 1B,中文字符通常是两个字节(Node.js 中使用三个字节)
1 byte = 8 bit


而 Buffer 处理的是字节 , 从 0 开始计数,Buffer 中的 255 标识一个每位都是 1 的字节

Buffer 特性

Buffer 类的实例类似于 0 到 255 之间的整型数组(其他整数会通过 & 255 操作强制转换到此范围),Buffer 是一个 JavaScript 和 C++ 结合的模块,对象内存不经 V8 分配,而是由 C++ 申请、JavaScript 分配。缓冲区的大小在创建时确定,不能调整。

Buffer 对象实在过于常用,被直接内置到全局变量中,使用时候无需 require 引入

实例化 Buffer

在 Node.js v6 之前都是通过调用构造函数的方式实例化 Buffer,根据参数返回不同结果。处于安全性原因,这种方式在 v6 后的版本中已经被废除,现在提供了三个职责清晰的函数处理实例化 Buffer 的工作

Buffer.from

Buffer.from 支持四种参数类型

  • Buffer.from(string [, encoding]):返回一个包含给定字符串的 Buffer
  • Buffer.from(buffer):返回给定 Buffer 的一个副本 Buffer
  • Buffer.from(array):返回一个内容包含所提供的字节副本的 Buffer,数组中每一项是一个表示八位字节的数字,所以值必须在 0 ~ 255 之间,否则会取模
  • Buffer.from(arrayBuffer):返回一个与给定的 ArrayBuffer 共享内存的新 Buffer
  • Buffer.from(object[, offsetOrEncoding[, length]]):取 object 的 valueOf  或 Symbol.toPrimitive 初始化 Buffer
// 16 进制显示值

const buf1 = Buffer.from('test', 'utf-8'); // <Buffer 74 65 73 74>

const buf2 = Buffer.from(buf1); // <Buffer 74 65 73 74>,buf1 副本,修改 buf2 不会影响 buf1

const buf3 = Buffer.from([256, 2, 3]); // <Buffer 00 02 03>,超过 255 会取模

const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;

// 和 arr 共享内存
const buf4 = Buffer.from(arr.buffer);
console.log(buf4); // <Buffer 88 13 a0 0f>

// 修改 arr
arr[1] = 6000;
console.log(buf4); // <Buffer 88 13 70 17> buf4 也受到影响

const buf = Buffer.from(new String('this is a test'));
// <Buffer 74 68 69 73 20 69 73 20 61 20 74 65 73 74>

Buffer.alloc(size [, fill [, encoding]])

分配一个大小为 size 字节的新 Buffer,如果 fill 为 undefined,则用 0 填充 Buffer

  • size 新 Buffer 的所需长度
  • fill | | | 用于预填充新 Buffer 的值。默认值: 0
  • encoding 如果 fill 是一个字符串,则这是它的字符编码。默认值: utf8
const buf = Buffer.alloc(5);
console.log(buf); // <Buffer 00 00 00 00 00>

如果指定了 fill,则分配的 Buffer 通过调用 buf.fill(fill)  进行初始化。

const buf = Buffer.alloc(5, 'a');
console.log(buf); // <Buffer 61 61 61 61 61>

Buffer.allocUnsafe(size)

分配一个大小为 size 字节的新 Buffer,allocUnsafe 执行速度比 alloc 快,但方法的名字听起来很不安全,确实也不安全

const buf = Buffer.allocUnsafe(10);
console.log(buf); // 打印内容不确定


Buffer 模块会预分配一个内部的大小为 Buffer.poolSize 的 Buffer 实例,作为快速分配的内存池,用于使用 allocUnsafe() 创建新的 Buffer 实例

alloc 永远不会使用内部的 Buffer 池,而 allocUnsafe 在 size 小于或等于 Buffer.poolSize 的一半时将会使用内部的 Buffer池。 当调用 allocUnsafe 时分配的内存段尚未初始化(不归零),这样分配内存速度很块,但分配到的内存片段可能包含旧数据。如果在使用的时候不覆盖这些旧数据就可能造成内存泄露,虽然速度快,尽量避免使用

Buffer.allocUnsafeSlow(size)

创建一个大小为 size 字节的新 Buffer,直接通过c++进行内存分配,不会进行旧值填充,当分配的空间小于4KB的时候,allocUnsafe 会直接使用预分配的 Buffer,因此速度比 allocUnsafeSlow 要快,当大于等于4KB的时候二者速度相差无异

编码

Buffer 目前支持以下几种编码格式

  • ascii
  • utf8
  • utf16le
  • base64
  • binary
  • hex

Buffer 和 string 转换

Buffer.from(string [, encoding]) 字符串转为 Buffer

const buf1 = Buffer.from('test', 'utf-8'); // <Buffer 74 65 73 74>


buf.toString([encoding[, start[, end]]])  Buffer 实例的 toString 方法可以将 Buffer 转为字符串

const buf1 = Buffer.from('test', 'utf-8');
console.log(buf1.toString()); // test
console.log(buf1.toString('hex')); // 74657374

Buffer 拼接

Buffer.concat(list[, totalLength]) 方法可以把多个 Buffer 实例拼接为一个 Buffer 实例

const buf1 = Buffer.from('a');
const buf2 = Buffer.from('b');
const buf3 = Buffer.from('c');

const buf = Buffer.concat([buf1, buf2, buf3]);
console.log(buf); // <Buffer 61 62 63>

StringDecoder

在 NodeJS 中一个汉字由三个字节表示,如果我们处理中文字符的时候使用了不是3的倍数的字节数就会造成字符拼接乱码问题

const buf = Buffer.from('中文字符串!');

for(let i = 0; i < buf.length; i+=5){
  var b = Buffer.allocUnsafe(5);
  buf.copy(b, 0, i);
  console.log(b.toString());
}

这样可以看到结果中出现了乱码
image.png

使用 string_decoder 模块可以解决这个问题

const StringDecoder = require('string_decoder').StringDecoder;
const decoder = new StringDecoder('utf8');
const buf = Buffer.from('中文字符串!');
for(let i = 0; i < buf.length; i+=5){
  var b = Buffer.allocUnsafe(5);
  buf.copy(b, 0, i);
  console.log(decoder.write(b));
}


StringDecoder 在得到编码后,知道宽字节在 utf-8 下占3个字节,所以在处理末尾不全的字节时,会保留到第二次 write()。目前只能处理UTF-8、Base64 和 UCS-2/UTF-16LE。

Buffer 其它常用 API

还有一些 Buffer 常用的 API,比较简单不用代码示例了

  • Buffer.isBuffer:判断对象是否为 Buffer
  • Buffer.isEncoding:判断 Buffer 对象编码
  • buf.length:返回 内存为此 Buffer 实例所申请的字节数,并不是 Buffer 实例内容的字节数
  • buf.indexOf:和数组的 indexOf 类似,返回某字符串、acsii 码或者 buf 在改 buf 中的位置
  • buf.copy:将一个 buf 的(部分)内容复制到另外一个 buf 中


了解了这些内容就可以进入 stream 的学习了

极简 Node.js 入门系列教程:www.yuque.com/sunluyong/n…

本文更佳阅读体验:www.yuque.com/sunluyong/n…