浅析ArrayBuffer、TypedArray和Buffer

·  阅读 6003

前言

接触node后,少不了会对文件进行操作,于是你可能会遇到以下问题:

  • 在写请求头时,有一个叫作返回响应数据的类型的属性(responseType),支持一个值"arrayBuffer",那arrayBuffer是什么呢?
  • 在node中处理文件时,经常遇到buffer。比如使用fs.readFile()去读文件时,第一个参数的类型是可以是Buffer,那Buffer是什么呢?
  • 在node的介绍Buffer的官方文档有提到一个很重要的东西TypedArray,那TypedArray又是什么呢?

如果你对这三个问题的答案了然于心,那么接下来的文章可以不用看了。如果有疑问的话,可以往下看,说不定就能帮你解决疑问。

二进制数组

首先你需要了解一下二进制数组,这对于我们搞明白上面三个问题非常重要。

是什么:用于处理二进制数据的类。

为什么存在:javaScript与显卡通信的时候,大量的实时的数据交互,用文本格式需要进行格式转化,二进制则省去转化时间。

和数组的关系:buffer的实例类似与整数数组,但是buffer的大小是固定不变的。

二进制数组由三类对象组成:

  1. ArrayBuffer对象:代表内存之中的一段二进制数据,本身不能直接操作内存,需要通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
  2. TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。
  3. DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

一波硬解答:

  • ArrayBuffer对象代表原始的二进制数据
  • TypedArray视图用来读写简单类型的二进制数据(ArrayBuffer),DataView视图用来读写复杂类型的二进制数据(ArrayBuffer)。
  • Node中的Buffer类是以更优化和更适合Nodejs的方式实现了Uint8Array API,意思就是Buffer类其实是TypedArray(Uint8Array)的nodejs实现。

至此,上面三个问题应该都解决了。想了解更多的可以继续往下看。

ArrayBuffer

ArrayBuffer对象代表储存二进制数据的一段内存,它不能直接读写,只能通过视图进行操作。

ArrayBuffer也是一个构造函数,可以分配一段可以存放数据的连续内存

const buffer = new ArrayBuffer(12); // 生成一个可以12个字节的连续内存,每个字节的默认值是0
复制代码

以DataView视图方式读取

const buffer = new ArrayBuffer(12);
const dataView = new DataView(buffer); // 对一段12字节的内存建立DataView视图
dataView.getUint8(0); // 0 以Uint8方式读取第一个字节
复制代码

以TypedArray视图方式读取,和DataView不同的是DataView是一个构造函数,TypedArray则是一组构造函数

const buffer = new ArrayBuffer(12);
const x1 = new Uint8Array(buffer); // 建立Uint8Array视图
const x2 = new Int32Array(buffer); // 建立Int32Array视图
x1[0] = 1;
x2[0] = 2;
// 由于两个视图是对应的是同一段内存,所以其中一个视图更改了内存,会影响到另一个视图
x1[0]; // 2

复制代码

ArrayBuffer对象还拥有byteLength属性和slice方法(此方法是ArrayBuffer对象上唯一可以读写内存的方法)。

ArrayBuffer有一个静态方法isView,判断参数是否为视图实例。

const buffer = new ArrayBuffer(12);
buffer.byteLength; // 12
buffer.slice(0, 3); // 用法和数组一致,拷贝buffer的前三个字节生成一个新的ArrayBuffer对象
ArrayBuffer.isView(buffer); // false
const dataView = new DataView(buffer);
ArrayBuffer.isView(dataView); // true
复制代码
复合视图

由于视图的构造函数可以指定起始位置和长度,所以在同一段内存之中,可以依次存放不同类型的数据,这叫做“复合视图”。

const buffer = new ArrayBuffer(12);
const a = new Uint8Array(buffer, 0, 1); // 以Uint8Array读取第一个字节
a[0] = 1;
const b = new Int32Array(buffer, 1, 2); // 以Int32Array读取第二个字节
b[0] = 2;
复制代码

视图

视图是什么:ArrayBuffer对象可以存储多种类型的数据。不同类型的数据有不同的解读方式,这就叫视图

视图的作用:以指定格式解读二进制数据

TypedArray

TypedArray一共包含九种类型,每一种都是一个构造函数。(DataView视图支持除Uint8ClampedArray以外的八种)

名称占用字节描述
Int8Array18位有符号整数
Uint8Array18位无符号整数
Uint8ClampedArray18位无符号整型固定数组(数值在0~255之间)
Int16Array216位有符号整数
Uint16Array216位无符号整数
Int32Array432 位有符号整数
Uint32Array432 位无符号整数
Float32Array432 位 IEEE 浮点数
Float64Array864 位 IEEE 浮点数
可接受的参数
  • 视图的构造函数接受三个参数,第一个ArrayBuffer对象,第二个视图开始的字节号(默认0),第三个视图结束的字节号(默认直到本段内存区域结束)
  • 接受ArrayBuffer实例作为参数,以指定格式读出二进制数据
  • 可接受普通数组作为参数,直接分配内存生成ArrayBuffer实例,并同时对这段内存进行赋值,再根据这段内存生成视图。
  • 可接受视图做为参数,生成的新数组复制了参数视图的值,生成新的数组和视图。(想基于同一内存生成新视图,需要传入视图.buffer)
和数组的区别
  • TypedArray内的成员只能是同一类型
  • TypedArray成员是连续的,不会有空位
  • TypedArray成员的默认值为0,数组的默认值为空
  • TypedArray只是视图,本身不存储数据,数据都存储在底层的ArrayBuffer中,要获取底层对象必须使用buffer属性
  • TypedArray可以直接操作内存,不需要进行类型转换,所以比数组快
  • TypedArray数组有的方法都可以使用,但不能使用cancat方法

DataView

如果一段数据包含多种类型的数据,除了使用复合视图的方式读取之外,还可以使用DataView视图读取。

DataView视图提供更多操作选项,而且支持设定字节序。本来,在设计目的上,ArrayBuffer对象的各种TypedArray视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;而DataView视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。

大端字节序和小端字节序,x86体系的计算机都使用小端字节序,123456中12比较重要,所以排在后面,存储顺序是563412。大端则相反

  • 一系列get方法用来读取内存

    const buffer = new ArrayBuffer(24);
    const dv = new DataView(buffer);
    // 接受一个参数
    const v1 = dv.getUint8(0); // 从第一个字节读取一个8位无符号整数
    // 读两个字节以上时,需要明确数据的存储模式(true:小端/false:大端)
    const v1 = dv.getUint16(1, true); // 从第二个字节开始,读取一个16位无符号整数(长度两个字节)
    复制代码
  • 一些列set方法用来写入内存

    // 接受三个参数,1.字节序号,2.写入的数据,3.写入方式(true:小端/false|undeifined:大端)
    dv.setInt32(0, 25, false); // 在第一个字节以大端字节序写入一个值为25的32位整数
    复制代码

Node中的Buffer

在引入TypedArray之前,js没有读取或操作二进制数据流的机制。Buffer类是作为nodejs API的一部分引入的,用于在TCP流,文件操作系统,以及上下文中与八位字节流进行交互。

现在可以使用 TypedArrayBuffer 类以更优化和更适合 Node.js 的方式实现了 Uint8Array API。

buffer类的实例类似于从0到255之间的整数数组(其他整数会通过& 255操作强制转换到范围内)

buffer.from()

  • buffer.from(array),返回包含给定八位字节数组的副本的新buffer
  • buffer.from(buffer),返回包含给定buffer副本的新buffer
  • buffer.from(arrayBuffer[, betyOffset[, len]]),返回与给定arrayBuffer共享同一段内存的新Buffer
  • buffer.from(string),返回包含给定字符串副本的新Buffer
  • buffer.alloc(size),返回一个指定大小的新建的已经初始化的Buffer
  • buffer.allocUnsafe(size)/buffer.allocUnsafeslow(size),返回一个指定大小的新建的未初始化(没有被清零)的buffer

初始化较慢,但是能保证新建的buffer实例不包含敏感数据

如果size小于或等于buffer.poolsize的一半,则allocUnsafe生成的buffer实例可能是从共享的内部内存池分配,allocUnsafeslow则从来不使用共享的内部内存池

buffer与typedArray

  • Buffer 实例也是 Uint8Array 实例,但是与 TypedArray 有微小的不同。(buffer可以类比为视图)

  • Buffer.from()TypedArray.from() 有着不同的实现。具体来说,typedArray.form()可接受第二个参数为映射函数,buffer.form()不行

参考文档:

ECMAScript入门--ArrayBuffer

Node.js中文网--Buffer

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改