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

本文更佳阅读体验: https://www.yuque.com/sunluyong/node/buffer

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

bit 与 Byte

  • bit 是我们常说的比特,比特币就是以此命名的,bit 是二进制的最小信息单位,1 bit 就是我们说的 1位,64 位操作系统 CPU一次能处理 位的数据
  • 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
  • utf16le
  • base64
  • binary
  • 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 的学习了