Node.js - Buffer模块API整理

?

最近的几篇文章中都涉及到了Buffer, 虽然用过几个API, 仍是系统总结一下, 主要针对API, 具体使用会少一点web

Remote Procedure Calljson

WebSocket协议以及ws源码分析数组

为何使用Buffer?bash

在引入 TypedArray 以前,JavaScript 语言没有用于读取或操做二进制数据流的机制。 Buffer 类是做为 Node.js API 的一部分引入的,用于在 TCP 流、文件系统操做、以及其余上下文中与八位字节流进行交互。网络

最经常使用的场景在于:函数

  • TCP流: 目前遇到用在封包和解包阶段, 提到的三篇文章中都有涉及源码分析

    • WebSocket数据帧
    • RPC通信协议
    • 等等等等
  • 文件系统操做学习

参考ui

网络序?本地序?傻傻分不清楚。。。spa

1. 建立Buffer

1.1 alloc家族

单纯建立大小为size字节的新Buffer

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

    • 分配的buffer会经过buf.fill进行初始化, 能确保永远不会包含敏感数据

    • 由于有初始化操做, 所以速度相较于allocUnsafe慢得多

  • Buffer.allocUnsafe(size)

    • 分配的buffer内存是未初始化的, 可能包含敏感数据
  • Buffer.allocUnsafeSlow(size)

    • 分配的buffer内存是未初始化的, 可能包含敏感数据

    • 建立一个非内存池的buffer, 避免垃圾回收机制因建立太多独立的buffer而过分使用

三者选择:

// 须要一个全新的buffer
Buffer.alloc

// 须要一个buffer, 不在乎有没有敏感数据, 反正都会被覆盖, 更经常使用
Buffer.allocUnsafe

// 须要一个须要再内存池中保留的buffer
Buffer.allocUnsafeSlow
复制代码

1.2 from五胞胎

  • Buffer.from(string[, encoding])

    • 从字符串建立一个buffer, 是最经常使用的
  • Buffer.from(buffer)

    • buffer建立一个buffer, 经常使用于复制
  • Buffer.from(array)

    • 从数字数组常见一个buffer, 若是不是数字, 自动被0填充
  • Buffer.from(arrayBuffer[, byteOffset[, length]]): 由于没用过, 记了也没价值

  • Buffer.from(object[, offsetOrEncoding[, length]])

    • 从对象建立一个buffer, 实际相似于Buffer.from(string)

    • 本质上是调用对象的Symbol.toPrimitivevalueOf方法

栗子1: Buffer.from(object)

class A {
  [Symbol.toPrimitive]() {
    return 'A'
  }
}
Buffer.from(new A(), 'utf-8')
// 打印 <Buffer 41>
复制代码

栗子2: Buffer.from(array)

Buffer.from(['a', '1', 2, 'b', '3'])
// 打印 <Buffer 00 01 02 00 03>
复制代码

2. Buffer的迭代

Buffer实例可使用for..of语法迭代, 相关的函数还有:

  • buf.keys()

  • buf.values()

  • buf.entries()

不过这样很是无聊

const buf = Buffer.from([1, 2, 3]);

for (const b of buf) {
  console.log(b);
}
// 打印:
//   1
//   2
//   3
复制代码

索引操做符都比这有趣: 索引操做符 [index] 可用于获取或设置 buf 中指定位置的字节

const buf = Buffer.from('Calabash')

console.log(buf[0]) // 67
复制代码

3. 两个buffer间的碰撞

  • buf.compare

    • 主要用于 Buffer 数组的排序, 建议跳过
  • buf.copy(target[, targetStart[, sourceStart[, sourceEnd]]])

    • 拷贝buf中的某个区域的数据到target中的某个区域
  • buf.equals(otherBuffer)

    • 比较两个buffer是否具备彻底相同的字节
  • Buffer.concat(list[, totalLength])

    • 合并list中的Buffer数组

4. BE和LE世家

4.1 大小端

考虑一个w位(bit)的整数,位表示为[Xw-1, Xw-2, ... ,X1, X0],其中Xw-1是最高有效位, 而X0是最低有效位。假设w是8的倍数,这些位就能被分组成为字节,其中最高有效字节包含位[Xw-1, Xw-2, ... ,Xw-8], 而最低有效字节包含位[X7, X6, ... ,Xw-8], 大小端指的就是有效字节的排列方式

  • 小端法(little endian): 最低有效字节在最前面

  • 大端法(big endian): 最高有效字节在最前面

大端 vs 小端对比

// 假设后面两个字节二进制值为
1111 1111 0000 0001
// 转为十六进制为
0xff 0x01
// 大端输出 65281
console.log(Buffer.from([0xff, 0x01]).readUInt16BE(0).toString(10))
// 小端输出 511
console.log(Buffer.from([0xff, 0x01]).readUInt16LE(0).toString(10))
复制代码

TCP/IP为任意整数数据项定义了统一的网络字节顺序(network byte order):大端字节顺序

额外补充一点~

  • 网络序就是大端法字节顺序

  • 本地序依据机器类型,多是大端法字节顺序或者小端法字节顺序

4.2 读/写字节流

读字节流一共22个API, 其中包含以下两个只须要一个字节就能表示, 不涉及大小端的API

  • buf.readInt8: 读取一个有符号的8位整数, 1个字节

  • buf.readUint8: 读取一个无符号的8位整数, 1个字节

其余的数字类型包括:

  • 读取BigInt类型, 8个字节:

    • readBigInt64BE
    • readBigInt64LE
    • readBigUInt64BE
    • readBigUInt64LE
  • 读取64位双精度浮点数, 8个字节:

    • readDoubleBE
    • readDoubleLE
  • 读取32位浮点数, 4个字节:

    • readFloatBE
    • readFloatLE
  • 读取16位整数, 2个字节

    • readInt16BE
    • readInt16LE
    • readUInt16BE
    • readUInt16LE
  • 读取32位整数, 4个字节

    • readInt32BE
    • readInt32LE
    • readUInt32BE
    • readUInt32LE
  • 读取指定的字节数, 最高支持48位精度, 0~6个字节

    • readIntBE
    • readIntLE
    • readUIntBE
    • readUIntLE

写字节流的API与上方一致, 只须要将read换成write

4.3 交换大小端

一共涉及三个方法, 都会buf解析成无符号的x位整数的数组, 并以字节的顺序原地进行交换

  • swap16(): buf.length必须是2的倍数

  • swap32(): buf.length必须是4的倍数

  • swap64(): buf.length必须是8的倍数

例如:

// 65281
Buffer.from([0xff, 0x01]).readUInt16BE(0).toString(10)
// 511
Buffer.from([0xff, 0x01]).readUInt16LE(0).toString(10)
// 511
Buffer.from([0xff, 0x01]).swap16().readUInt16BE(0).toString(10)
复制代码

5. 小甜点

  • buf.toJSON: 返回buf的JSON格式, 当字符串化Buffer时, JSON.stringify()会调用该函数
const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
const json = JSON.stringify(buf);

console.log(json);
// 打印: {"type":"Buffer","data":[1,2,3,4,5]}
复制代码
  • buf.slice(start, end): 建立一个指向与原始Buffer同一内存得新Buffer, 但进行了裁剪, 注意两个对象所分配的内存是重叠的, 修改一个会影响另外一个

  • base64string的转换

// Y2FsYWJhc2g=
const base64Str = Buffer.from('calabash').toString('base64')
// calabash
Buffer.from(base64Str, 'base64').toString()
复制代码

6. END

到这里基本完成Buffer API的学习, 不过以我目前的水平, 没有须要本身封包解包的场景, 只有看别人封包解包了TAT

相关文章
相关标签/搜索