html5中二进制数据处理,JavaScript 读写二进制数据

类型化数组的出现

类型化数组是 HTML5 中引入的API,它能够让开发者使用 JavaScript 直接操作二进制数据。在类型化数组出现之前,我们是无法直接通过 JavaScript 操作二进制数据,通常都是操作 JavaScript 中的数据类型,由运行时转化成二进制。这就多了一个转化的过程,尽管 JavaScript 对数据类型做了很多优化以提高效率,但相比直接操作二进制来说,仍然有效率上的差异。于是类型化数组就顺势推出了。

用途

那么,类型化数组的应用场景都有哪些呢?

canvas 图像处理。

WebGL 与显卡通信。

文件操作

Ajax响应

如何使用

那么,既然类型化数组这么重要,那还等什么,赶紧来掌握它们吧。

既然我们要直接操作二进制数据,二进制数据又是存放在一段连续的内存区域中,所以我们首先要有这么一段内存区域。

我们可以创建一个内存区域:

let buffer = new ArrayBuffer()

复制代码

ArrayBuffer 是一个构造函数,允许我们实例化数组缓冲区,数组缓冲区可以理解为是一段连续的内存区域。

由于我们构造函数传入的参数是空,所以生成的 buffer 指向的内存长度是 0 字节,没有意义。

嗯,那我们就创建一个有意义的内存区域。

buffer = new ArrayBuffer(8)

复制代码

我们给ArrayBuffer 传入参数 8,意思是让浏览器帮我们创建一段 8 个字节长度的内存区域。

我们看下这段内存区域的长度是否是 8 个字节

console.log(buffer.byteLength);

复制代码

输出是 8, 看来浏览器没有欺骗我们。

71595ce50950754e2604014118525710.png

我们猜想这 8 个字节里面的值应该都是 0 ,因为我们并没有给 buffer 赋值。让我们确认一下吧,先看第一个字节:

console.log(buffer[0])

复制代码

输出: undefined。

咦?怎么是 undefined 呢?

哦,原来 buffer[0] 的意思是查看 buffer 这个对象 的属性为 0 的值,因为 buffer 没有 0 这个属性,所以是 undefined。

好吧,看来我们查看 buffer 内容的姿势不对。

那该如何查看 buffer 内容呢?

数组视图

珰珰珰珰,八大金刚闪亮登场~

Int8Array:8 位有符号整数,长度 1 个字节。

Uint8Array: 8位无符号整数, 1 个字节长度。

Int16Array:16位有符号整数, 2 个字节长度。

Uint16Array:16位无符号整数,2 个字节长度。

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

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

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

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

这八大金刚有什么神通呢?我们无法直接读写 buffer 数据,而这八种数据类型充当了读写 buffer 内容的桥梁。

df71b8aa60856dd05e3be989c4f41769.png

还是上面的 buffer,我们想查看一下 buffer 内容。首先创建一个读写该 buffer 的桥梁:

let int8Array = new Int8Array(buffer);

复制代码

我们创建了一个读写 buffer 的桥梁,用 8 位有符号整数来读写 buffer。那现在我们看看 buffer 第一位的内容是什么吧?

console.log(int8Array[0]);

复制代码

输出:0,看来初始化的时候,buffer 的各个字节存储的值默认都是 0 了。

021cc3a763d8a7dcfc1dfe476864c864.png

我们看看如何使用 Int8Array 给 buffer 赋值:

int8Array[0] = 30;

int8Array[1] = 41;

int8Array[2] = 52;

int8Array[3] = 63;

int8Array[4] = 74;

int8Array[5] = 85;

int8Array[6] = 86;

int8Array[7] = 97;

复制代码

很简单,因为 Int8Array 是一个字节的长度,和 buffer 的单位一致,所以我们可以通过索引的形式对 buffer 指定位置进行赋值操作。

7624433bbc37fe183f2ed5fc4ce98894.png

很简单吧?就是这么简单。

另外七大金刚和 Int8Array 的用法一样,但是有所不同,我们看下他们之间的区别。

这次我们使用 Int16Array,仍然是刚才的 buffer,我们创建一个新的桥梁,这座桥梁仍然通往 buffer 。

let int16Array = new Int16Array(buffer);

复制代码

大家试想一下 int16Array[0] 是什么内容?

我们输出一下:

console.log(int16Array[0])

复制代码

咦,结果怎么是 10526?

不太理解了。好吧,我们分析下 10526 怎么得来的。

我们看下 buffer 中的二进制数据。

由于 Int16Array 占两个字节,所以我们在用它读写数据的时候,一个索引所代表的数据等于 buffer 中两个字节。

2ca0eab56839e5ec41cfe82addad896f.png

我们可以看到 int16Array[0] 里面的二进制数据是由30的二进制和41的二进制数据拼接而成:00011110(30) 00101001(41)。

我们按照 30、41的顺序计算一下二进制对应的十进制数。

parseInt(1111000101001, 2) //输出 7721

复制代码

算出来的值是 7721,这和我们输出的不一致呀?

这就涉及到字节顺序的概念。在我们的个人笔记本上一般都是小端字节序。小端字节序体现在我们这个示例中即是 41、30的二进制顺序,我们刚才的计算顺序有问题,那按照 41、30 的二进制顺序计算一下

parseInt(10100100011110, 2) //输出 10526

复制代码

可以看到输出结果是 10526,和我们直接使用 int16Array[0]

得出的结果一致。

上面这个例子,告诉我们在换数据结构解析 buffer 的时候,数据会变得不容易理解,我们一定要谨慎处理。

属性和方法

类型化数组实例化的对象包含一些很有用的属性和方法:

length

length属性返回类型化数组的数据成员个数。

byteLength

返回类型化数组的字节长度。注意与length的区别。通长 byteLength = length * 每个数据占用字节数

byteOffset

返回该类型化数组的数据从所处 buffer 中的哪个字节开始。

buffer

类型化数组对应的 buffer。

set

复制数组,将某段内存中的数据完整地复制到另一段内存。

let a = new Uint8Array(12);

a[0] = 31;

a[1] = 32;

let b = new Uint8Array(12);

b.set(a);

复制代码

上面这段代码的意思是将 a 这段buffer中的内容,完整地拷贝到 b 这段 buffer 中,这种方式比按索引赋值要快速地多。

当然,set 支持从某个索引开始复制数据

let a = new Uint8Array(12);

a[0] = 31;

a[1] = 32;

let b = new Uint8Array(10);

b.set(a, 2);

复制代码

上面这段代码意思是从b的第三个索引位置开始复制 a 中的数据。

subarray

subarray的意思是对一个类型化数组,取其子数组的内容,返回一个新的类型化数组。

let a = new Uint8Array(8);

a[2] = 1;

let b = a.subarray(2,3);

console.log(b.length);

console.log(b.byteLength);

复制代码

subarray 的第一个参数,代表从源数组的第几个索引开始截取,第二个参数代表截取到第几个索引。

混合视图

有一点需要注意,我们的类型数组初始化的时候,可以指定 buffer的某一段,这就意味着,我们可以对一段 buffer 内存区域指定多个类型数,我们称之为 混合视图。

let buffer = new Buffer(8);

let idArray = new Int8Array(buffer, 0,2);

let nameArray = new Int8Array(buffer, 2, 4);

let ageArray = new Int8Array(buffer, 6, 2);

复制代码

我们用一段内存区域表示一个人的 id、name、age。这种结构类似于 C 语言中的 struct 。

e9b865d58e3abefc318d643b06bd0ef0.png

我们将一段 8 个字节的内存分成三个部分:

字节 0 ~ 字节 1 代表 id。

字节 2 ~ 字节 5 代表 username。

字节 6 ~ 字节 7 代表 age。

DataView

JavaScript 还引入了另外种视图DataView,也能达到操作 buffer 的目的,但相比之下,DataView 操作粒度更细一些,而且还能够设置字节序为大端还是小端。

DataView 的构造函数:

DataView(ArrayBuffer对象 buffer, 从 buffer 的第几个字节开始读取, 读取的长度);

复制代码

举个例子来说:

let buffer = new ArrayBuffer(10);

let view = new DataView(buffer);

复制代码

如何读取?

我们创建好了视图 view, 那该如何读取呢?

getInt8(index, order):从第 index 个字节读取一个 8 位整数。

getUint8(index, order):从第 index 个字节开始读取一个无符号的 8 位整数。

getInt16(index, order):从第 index 个字节开始读取 2 个字节,返回一个 16 位整数。

getUint16(index, order):从第 index 个字节开始读取 2 个字节,返回一个无符号的 16 位整数。

getInt32(index, order):从第 index 个字节开始读取 4 个字节,返回一个32位的整数。

getUint32(index, order):从第 index 个字节开始读取 4 个字节,返回一个无符号的 32 位整数。

getFloat32(index, order):从第 index 个字节开始读取 4 个字节,返回一个 32 位 浮点数。

getFloat64(index, order):从第 index 个字节开始读取 8 个字节,返回一个 64 位的浮点数。

JavaScript 提供了 8 种读取方式,功能很简单,也很容易理解,这里就不一一做示例了,大家可以自己试一下。

刚刚我们也说了,DataView 也支持设置字节序,在上面 8 中读取方式中,第一个字节是索引,第二个字节允许我们设置字节序,true 代表 小端字节序读取,false 代表大端字节序读取,默认为 false。

如何写入?

DataView 不仅能支持细粒度的读取操作,也支持细粒度的写入操作:

setInt8(index, value, order):从第 index 个字节开始,写入 1 个字节的值为 value 的 8 位整数。

setUint8(index, value, order):从第 index 个字节开始,写入 1 个字节的值为 value 的无符号 8 位整数。

setInt16(index, value, order):从第 index 个字节开始,写入 2 个字节的值为 value 的 16 位整数。

setUint16(index, value, order):从第 index 个字节开始,写入 2 个字节的值为 value 的无符号的 16 位整数。

setInt32(index, value, order):从第 index 个字节开始,写入 4 个字节的值为 value 的 32 位整数。

setUint32(index, value, order):从第 index 个字节开始,写入 4 个字节的值为 value 的无符号 32 位整数。

setFloat32(index, value, order):从第 index 个字节开始,写入 4 个字节的值为 value 的 32 位浮点数。

setFloat64(index, value, order):从第 index 个字节开始,写入 8 个字节的值为 value 的 64 位浮点数。

order 的意思仍然是设置写入时的字节序, true 为小端字节序,false 为大端字节序,默认为 false。

用法也很简单,大家可以练习一下。

至此,关于 JavaScript 操作二进制的方式就介绍完了,大家以后碰到需要直接操作内存的场景时,不妨用用这两种方式。

未来的你会感谢今天努力的你。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebSocket 协议可以传输二进制数据和文本数据。在处理二进制数据时,我们需要使用 BinaryWebSocketHandler 类来处理二进制数据。 具体步骤如下: 1. 创建 BinaryWebSocketHandler 类并重写 handleBinaryMessage 方法来处理二进制数据。 ```java public class MyBinaryWebSocketHandler extends BinaryWebSocketHandler { @Override public void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { // 处理二进制消息 } } ``` 2. 修改 WebSocketConfig 类注册 WebSocket 处理器的方法,将处理器修改为 BinaryWebSocketHandler 类型,并指定 WebSocket 路径。 ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(binaryWebSocketHandler(), "/ws").setAllowedOrigins("*"); } @Bean public WebSocketHandler binaryWebSocketHandler() { return new MyBinaryWebSocketHandler(); } } ``` 3. 在前端页面使用 WebSocket 发送二进制数据。 ```javascript var socket = new WebSocket("ws://localhost:8080/ws"); socket.binaryType = "arraybuffer"; socket.onopen = function() { // 连接成功 }; socket.onmessage = function(event) { var data = new Uint8Array(event.data); // 处理二进制消息 }; socket.onclose = function() { // 连接关闭 }; var data = new Uint8Array([1, 2, 3, 4]); socket.send(data.buffer); ``` 在前端页面,我们需要通过设置 socket.binaryType = "arraybuffer" 来告诉 WebSocket 接收二进制数据。在接收到二进制数据时,我们需要将 event.data 转换为 Uint8Array 类型来处理二进制数据。 注意:在发送二进制数据时,需要通过 data.buffer 来获取 ArrayBuffer 类型的数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值