前言
接触node后,少不了会对文件进行操作,于是你可能会遇到以下问题:
- 在写请求头时,有一个叫作返回响应数据的类型的属性(responseType),支持一个值
"arrayBuffer",那arrayBuffer是什么呢? - 在node中处理文件时,经常遇到
buffer。比如使用fs.readFile()去读文件时,第一个参数的类型是可以是Buffer,那Buffer是什么呢? - 在node的介绍Buffer的官方文档有提到一个很重要的东西
TypedArray,那TypedArray又是什么呢?
如果你对这三个问题的答案了然于心,那么接下来的文章可以不用看了。如果有疑问的话,可以往下看,说不定就能帮你解决疑问。
二进制数组
首先你需要了解一下二进制数组,这对于我们搞明白上面三个问题非常重要。
是什么:用于处理二进制数据的类。
为什么存在:javaScript与显卡通信的时候,大量的实时的数据交互,用文本格式需要进行格式转化,二进制则省去转化时间。
和数组的关系:buffer的实例类似与整数数组,但是buffer的大小是固定不变的。
二进制数组由三类对象组成:
- ArrayBuffer对象:代表内存之中的一段二进制数据,本身不能直接操作内存,需要通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。
- TypedArray视图:共包括 9 种类型的视图,比如
Uint8Array(无符号 8 位整数)数组视图,Int16Array(16 位整数)数组视图,Float32Array(32 位浮点数)数组视图等等。 - DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。
一波硬解答:
ArrayBuffer对象代表原始的二进制数据TypedArray视图用来读写简单类型的二进制数据(ArrayBuffer),DataView视图用来读写复杂类型的二进制数据(ArrayBuffer)。- Node中的
Buffer类是以更优化和更适合Nodejs的方式实现了Uint8ArrayAPI,意思就是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以外的八种)
| 名称 | 占用字节 | 描述 |
|---|---|---|
| Int8Array | 1 | 8位有符号整数 |
| Uint8Array | 1 | 8位无符号整数 |
| Uint8ClampedArray | 1 | 8位无符号整型固定数组(数值在0~255之间) |
| Int16Array | 2 | 16位有符号整数 |
| Uint16Array | 2 | 16位无符号整数 |
| Int32Array | 4 | 32 位有符号整数 |
| Uint32Array | 4 | 32 位无符号整数 |
| Float32Array | 4 | 32 位 IEEE 浮点数 |
| Float64Array | 8 | 64 位 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流,文件操作系统,以及上下文中与八位字节流进行交互。
现在可以使用 TypedArray, Buffer 类以更优化和更适合 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()不行
参考文档: