1
Buffer是一个类数组对象,里面存储的是字节,有点类似于字节数组,我们常用的场景是网络请求时对流数据的处理或文件操作时对字符串的处理。

在Node中,应用需要处理网络协议、操作数据库、处理图片、接收上传文件等,在网络流和文件的操作中,还要处理大量二进制数据,JavaScript自有的字符串远远不能满足这些需求(javascript字符串是utf-8存储的,处理二进制数据能力很弱,而网络层对于不同资源的请求响应和文件都是二进制数据来交互的),于是Buffer对象应运而生,nodejs提供了这么一个接口,来创建一个专门存放二进制数据的缓存区,并提供一些方法对缓存区数据进行处理。

Buffer(直译 缓冲,nodejs中用于处理二进制的数据)在nodejs中可以全局访问,不需要用require关键字加载

Buffer是一个像Array的对象,但它主要用于操作字节。

图片描述

我们可以发现,Buffer是一个对象,也是一个构造函数,具有自己的属性和静态方法,通过它new出来的实例,代表可以把引擎分配出一段内存,基本是一段数组,它的元素为16进制的两位数,即0到255的数值。

我们可以从模块结构对象结构层次两方面来认识。

  • 模块结构

Buffer是一个典型的JavaScript与C++结合的模块,性能相关部分用C++实现,飞性能相关部分用JavaScript实现。

  • buffer对象
1.new Buffer(size):分配一个新的 buffer 大小是 size 的8位字节. 
2.new Buffer(array):分配一个新的 buffer 使用一个8位字节 array 数组. 
3.new Buffer(str, [encoding]):encoding String类型 - 使用什么编码方式,参数可选.

4.类方法: Buffer.isEncoding(encoding):如果给定的编码 encoding 是有效的,返回 true,否则返回 false。 
5.类方法: Buffer.isBuffer(obj):测试这个 obj 是否是一个 Buffer. 返回Boolean
6.类方法: Buffer.concat(list, [totalLength]):list {Array}数组类型,Buffer数组,用于被连接。totalLength {Number}类型 上述Buffer数组的所有Buffer的总大小。
> new Buffer ('Hello 镜心的小树屋','base64')
<Buffer 1d e9 65 a0>

图片描述

  • Buffer 内存分配

Buffer对象的内存分配不是在V8的堆内存中,而是在Node的C++层面实现内存的申请,在Javascript中分配内存。
为了高效使用申请的内存,Node采用slab分配机制。
slab就是一块申请好的固定大小的内存区域,slab有三种状态:

  • full:完全分配状态
  • partial:部分分配状态
  • empty: 没有分配状态

当我们需要一个Buffer对象,可以通过以下方式分配指定大小Buffer对象

new Buffer(size)

Node以8KB为界限来区分Buffer是大对象还是小对象
图片描述

这个8KB的值就是每个slab的大小值,在JavaScript层面,以它作为单位单元进行内存的分配

  • 写入缓冲区
var buffer = new Buffer(8);//创建一个分配了8个字节内存的缓冲区
console.log(buffer.write('a','utf8'));//输出1

这会将字符"a"写入缓冲区,node返回经过编码以后写入缓冲区的字节数量,这里的字母autf-8编码占用1个字节。

  • 复制缓冲区

Node.js提供了一个将Buffer对象整体内容复制到另一个Buffer对象中的方法。我们只能在已经存在的Buffer对象之间复制,所以必须创建它们。

buffer.copy(bufferToCopyTo)

其中,bufferToCopyTo是要复制的目标Buffer对象。如下示例:

var buffer1 = new Buffer(8);
buffer1.write('nice to meet u','utf8');
var buffer2 = new Buffer(8);
buffer1.copy(buffer2);
console.log(buffer2.toString());//nice to meet u

进一步了解 看这篇 https://github.com/ElemeFE/no...

ES6的TypedArray

图片描述

Buffer concat vs String concat

源码 => 仓库

const fs = require('fs')

// 从某个文件创建一个字节流
const stream = fs.createReadStream('test.md', {highWaterMark: 11})
stream.setEncoding('utf8')

var data = ''
stream.on('data', function (chunk) {
  data += chunk
})
// console.log('dasddd',stream)

stream.on('end', function () {
  console.log(data)
})

输出

clipboard.png

面试常见问题

Buffer 一般用于处理什么数据? 其长度能否动态变化? more

注意以上有api已经废弃

buffer.alloc

const buf = Buffer.alloc(1024*1024); //分配一块 1M的内存
let offset = 0;

// 开始编码
offset = 0; // 重置偏移量
buf[0] = 0;
// 我们在开发网络通讯协议的时候操作 Buffer 都应该用大端序的 API,也就是 BE 结尾的。
buf.writeInt32BE(1000, 1);
buf[5] = 1; //codec => 1 代表是JSON 序列化

offset += 10;

const payload = {
  service: 'com.alipay.nodejs.HelloService:1.0',
  methodName: 'plus',
  args: [ 1, 2 ],
}

const bodyLength = buf.write(JSON.stringfy(payload), offset);
buf.writeInt32BE(bodyLength, 6);
offset += bodyLength;
buf.slice(0, offset); // 返回  

buf.writeInt32BE

buf.write

buf.slice

浅拷贝,类似Array.slice()
建一个指向与原始 Buffer 同一内存的新 Buffer,但使用 start 和 end 进行了裁剪。

修改新建的 Buffer 切片,也会同时修改原始的 Buffer,因为两个对象所分配的内存是重叠的。

Buffer.concat

/** 
 * 正确拼接Buffer
 * 
 * 正确的拼接方式是用一个数组来储存接收到的所有Buffer片段并记录下所有片段的总长度
 * 然后调用Buffer.concat()方法生成一个合并的Buffer对象。Buffer.concat()方法封装了从小Buffer对象向大Buffer对象的复制过程,实现十分细腻
 * @list 要合并的Buffer数组 或 Uint8Array数组
 * @length 合并后的Buffer的总长度
 */

Buffer.concat = function (list, length) {
  if(!Array.isArray(list)) {
    throw new Error('Usage: Buffer.concat(list, [length])')
  }

  if(list.length === 0) {
    return Buffer.alloc(0)
  } else if (list.length === 1) {
    return list[0]
  } 

  if(typeof length !== 'number') {
    length = 0
    for (let i = 0; i < list.length; i++) {
      let buf = list[i]
      length += buf.length
    }
  }

  let buffer = Buffer.alloc(length)
  var pos = 0;
  for (let i = 0; i < list.length; i++) {
    let buf = list[i]
    buf.copy(buffer, pos)
    pos+=buf.length
  }

  return buffer
}

Long类型的处理

JavaScript 的基本类型里面表示数字的只有 Number,它能够表达的整数范围是 -(2^53 - 1) ~ (2^53 - 1),而 Java 里面的 Long 类型的范围是 -(2^64 - 1) ~ (2^64 - 1)。那么在 RPC 调用中遇到 Long 类型我们该如何处理呢?

// 2^64-1 正确的值应该是 18446744073709551615,在 JS 引擎中运行得到的值却是 18446744073709552000
Math.pow(2, 64) - 1 // 18446744073709552000

首先我们得搞清楚 Long 类型是怎么存储的,一个 Long 数字占用 8 Bytes,我们可以把它拆分成两个 32 位整数(各占 4 Bytes)来表示,分别称之为「高位」和「低位」,低位存储的是长整形对 2^32 取模后的值,高位储存的是长整形整除 2^32 后的值,下面是几个 Long 类型整数用二进制的表示形式:

Long: 4294967296

High: 1     Low: 0
+-----------+-----------+
|00 00 00 01|00 00 00 00|
+-----------+-----------+
  
Long: 1000

High: 0     Low: 1000
+-----------+-----------+
|00 00 00 00|00 00 03 e8|
+-----------+-----------+
  
Long: 45565600000000

High: 10609 Low: 291956736
+-----------+-----------+
|00 00 29 71|11 66 e8 00|
+-----------+-----------+

那么在 JS 里面如何表示一个 Long 类型?这里我们使用了社区的 Long 模块,它可以通过 Number 或者字符串获得一个长整形,并且支持各种运算操作

const Long = require('long')
// const buf = new Buffer([ 0x00, 0x00, 0x29, 0x71, 0x11, 0x66, 0xe8, 0x00 ]);  
//  该api已经废弃,使用 Buffer.from()替代
const buf = Buffer.from([ 0x00, 0x00, 0x29, 0x71, 0x11, 0x66, 0xe8, 0x00 ])
let long = new Long(
  buf.readInt32BE(4),
  buf.readInt32BE(0),
)
long = long.add(1)
long.toString(); // 45565600000001
// const longBuf = new Buffer(long.toBytes()) // <Buffer 00 00 29 71 11 66 e8 01>
const longBuf = Buffer.from(long.toBytes())
Long.fromString('18446744073709551615') // Long { low: -1, high: -1, unsigned: false }

好用的包

uffer 原生 API 比较底层,对于用户不够友好,为了方便使用,社区封装了一个 一些模块:

常用关于buffer操作

https://github.com/shelljs/sh...

line 274


// Normalizes Buffer creation, using Buffer.alloc if possible.
// Also provides a good default buffer length for most use cases.
var buffer = typeof Buffer.alloc === 'function' ?
  function (len) {
    return Buffer.alloc(len || config.bufLength);
  } :
  function (len) {
    return new Buffer(len || config.bufLength);
  };
exports.buffer = buffer;

参考:

ArrayBuffer MDN
nodejs buffer api
《深入浅出 nodejs》朴灵
慕课网 进击Node.js基础(二)
nodejs Docs
https://github.com/ElemeFE/no...
聊聊 Node.js RPC(一)— 协议

未完待续。。。


白鲸鱼
1k 声望110 粉丝

方寸湛蓝