10分钟带你了解什么是ArrayBuffer?

2,750 阅读6分钟

前言

很多时候我们前端开发是用不到 ArrayBuffer 的,但是用不到 ArrayBuffer 不代表我们不需要了解这个东西。本文就围绕 ArrayBuffer 来讲一下相关知识,大概需要10分钟左右就可以读完本文。

什么是ArrayBuffer?

描述

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer MDN

ArrayBuffer具有如下的特性

  1. ArrayBuffer 是固定长度连续内存空间的引用;
  2. ArrayBuffer 对象代表存储一段二进制数据的内存;
  3. ArrayBuffer 并不能直接读写,只能通过视图(Typed Array View | Data View)来读写;

创建一个Buffer

ArrayBuffer 接收一个 length 参数用于描述要创建的ArrayBuffer的大小,单位是字节。length的值应该是大于0的整数,但是如果你传入的是一个小数,那么会自动向下取整去创建。

// 创建一个长度为0的buffer
const buffer_0 = new ArrayBuffer(0); // success
// 创建一个长度为8的buffer
const buffer_1 = new ArrayBuffer(8); // success
// 创建一个长度为3的buffer
const buffer_2 = new ArrayBuffer(3); // success

根据传入不同的长度,其视图也是不一样的(后面视图部分会详细介绍不同视图的区别),根据下图可以看到,buffer实例具有byteLength属性可以获取长度。

image.png

判断是否是视图实例

ArrayBuffer有两种视图用于提供读取、写入的能力,一种是Typed Array 类型视图一种是Data View。当我们需要判断一个参数是否是符合条件的视图时,可以用isView这个方法。具体判断如下:

ArrayBuffer.isView(new Uint8Array()); // true
ArrayBuffer.isView(buffer); // false

Typed Array 和 Data View 的区别?

什么是Typed Array ?

Typed Array是一个拥有9种类型的视图集合,每一种都是一个构造函数。

Int8Array:8位有符号整数,长度1个字节,范围是[-128 ~ 128]

Uint8Array:8位无符号整数,长度1个字节,范围是[0 ~ 255)

Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。

Int16Array:16位有符号整数,长度2个字节,范围是[-32768 ~ 32767]

Uint16Array:16位无符号整数,长度2个字节,范围是[0 ~ 65536)

Int32Array:32位有符号整数,长度4个字节。

Uint32Array:32位无符号整数,长度4个字节。

Float32Array:32位浮点数,长度4个字节。

Float64Array:64位浮点数,长度8个字节。

我们从开文知道ArrayBuffer存储的是字节长度,而一个字节是8位,同时根据上面每个视图的长度得知总的位数,因此可以推导出来每一个视图的取值范围具体是多少。具体的操作逻辑如下:

// 创建一个长度为8的buffer
const buffer = new ArrayBuffer(8);
// 创建一个无符号8位视图
const view = new Uint8Array(buffer);
// 赋值
view[0] = 1;
view[1] = 2;

结果如下:

image.png

从上面的结果中可以看到,Uint8Array 的结果是咱们赋值时候的具体值,但是 Int16Array 为什么会是513呢?我们接着往下看:

当我们执行 view[0] = 1 时,我们在内存中开辟了一个字节的内存,具体如下图:

image.png

当我们执行 view[0] = 2 时,我们在内存中开辟了一个字节的内存,具体如下图:

image.png

以上是存放在Uint8Array时候的,视图中每一个位置存放一个字节的数值。但是如果转换成Int16Array的时候,一个位置要存放2个字节的数值。所以其中第0个位置和第1个位置的值需要放置在一起,通过下图计算可得上图的513。

image.png

什么是Data View ?

相较于TypedArray,DataView对ArrayBuffer的操作更加灵活。因为TypedArray操作ArrayBuffer时每个元素占用的字节大小是固定的,要么8位要么16位或者32位等等。而DataView可以从ArrayBuffer中自由的读写多种数据类型,从而控制字节顺序。

创建一个Data View

const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);

创建视图后如下,可以看到原型上和(Typed View)类型视图是有区别的。

image.png

设置数值

原型上有以下方法用于写入和读取,

getInt8/setInt8

getUint8/setUint8

getInt16/setInt16

getUint16/setUint16

getInt32/setInt32

getUint32/setUint32

getFloat32/setFloat32

getFloat64/setFloat64

赋值代码如下:

const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setInt8(0, 1);
view.setInt8(1, 2);
view.setInt8(2, 100);
view.setInt8(5, 100);

效果如下:

image.png

ArrayBuffer与Blob的区别

ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区,Blob表示一个不可变、原始数据的类文件对象。

Blob类型只有slice方法,用于返回一个新的Blob对象,包含了原Blob对象中指定范围内的数据。

对比发现,ArrayBuffer的数据,是可以按照字节去操作的,而Blob的只能作为一个对象去处理。所以ArrayBuffer相对于Blob更接近真实的二进制,更底层。

ArrayBuffer与Blob的相互转化

var buffer = new ArrayBuffer(16)
var blob = new Blob([buffer])
var blob = new Blob([1,2,3,4,5])
var reader = new FileReader()
reader.readAsArrayBuffer(blob)
reader.onload = function(e) {
    // e.target.result 即为ArrayBuffer
}

ArrayBuffer使用场景

1.Ajax

传统上,服务器通过 AJAX 操作只能返回文本数据,即responseType属性默认为textXMLHttpRequest第二版XHR2允许服务器返回二进制数据,这时分成两种情况。如果明确知道返回的二进制数据类型,可以把返回类型(responseType)设为arraybuffer;如果不知道,就设为blob

let xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
  let arrayBuffer = xhr.response;
  // ···
};
xhr.send();

2.Canvas

网页Canvas元素输出的二进制像素数据,就是 TypedArray 数组。

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const uint8ClampedArray = imageData.data;

需要注意的是,上面代码的uint8ClampedArray虽然是一个 TypedArray 数组,但是它的视图类型是一种针对Canvas元素的专有类型Uint8ClampedArray。这个视图类型的特点,就是专门针对颜色,把每个字节解读为无符号的 8 位整数,即只能取值 0 ~ 255,而且发生运算的时候自动过滤高位溢出。这为图像处理带来了巨大的方便。

3.WebSocket

WebSocket可以通过ArrayBuffer,发送或接收二进制数据。

let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
// Wait until socket is open
socket.addEventListener('open', function (event) {
  // Send binary data
  const typedArray = new Uint8Array(4);
  socket.send(typedArray.buffer);
});
// Receive binary data
socket.addEventListener('message', function (event) {
  const arrayBuffer = event.data;
  // ···
});

4.Fetch API

Fetch API 取回的数据,就是ArrayBuffer对象。

fetch(url)
.then(function(response){
  return response.arrayBuffer()
})
.then(function(arrayBuffer){
  // ...
});

5.File API

如果知道一个文件的二进制数据类型,也可以将这个文件读取为ArrayBuffer对象。

const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
  const arrayBuffer = reader.result;
  // ···
};