找回密码
 会员注册
查看: 14|回复: 0

JS中的二进制数据处理

[复制链接]

2万

主题

0

回帖

7万

积分

超级版主

积分
72374
发表于 2024-10-6 12:05:21 | 显示全部楼层 |阅读模式
点击关注“有赞coder”获取更多技术干货哦~作者:大勾部门:业务技术/前端前言??在现有的计算机中,二进制常常以字节数组的形式存在于程序当中。例如在C#里面,就用byte[],标准C里面没有byte类型,但可以通过typedef把byte定义为unsigned char的别名,效果是一样的。JS设计之初似乎就没想过要处理二进制,对于字节的概念可以说是非常非常的模糊。如果要表达字节数组,那么似乎只能用一个普通数组来表示。??然而随着业务需求的逐渐发展,出现了WebGL这样的技术。所谓WebGL,就是指浏览器与显卡之间的通信接口。为了满足JavaScript与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。类型化数组(Typed Array)就是在这种背景下诞生的。而类型化数组是建立在ArrayBuffer对象的基础上的。下面介绍一下Arraybuffer。一、Arraybuffer1.1 基本概念??ArrayBuffer 对象是 ES6 才纳入正式 ECMAScript 规范,是 JavaScript 操作二进制数据的一个接口。ArrayBuffer 对象是以数组的语法处理二进制数据,也称二进制数组。它不能直接读写,只能通过视图(TypedArray视图和DataView视图)来读写。??ArrayBuffer 简单说是一片内存,但是你不能直接用它。这就好比你在 C 里面,malloc 一片内存出来,你也会把它转换成 unsigned_int32 或者 int16 这些你需要的实际类型的数组/指针来用。这就是 JS 里的 TypedArray 的作用,那些 Uint32Array 也好,Int16Array 也好,都是给 ArrayBuffer 提供了一个 “View”,MDN 上的原话叫做 “Multiple views on the same data”,对它们进行下标读写,最终都会反应到它所建立在的 ArrayBuffer 之上。?1.2 基本操作「语法」new ArrayBuffer(length)参数:length 表示要创建的 ArrayBuffer 的大小,单位为字节;返回值:ArrayBuffer 对象;异常:如果 length 大于 Number.MAX_SAFE_INTEGER(>= 2 ** 53)或为负数,则抛出一个 RangeError 异常;「示例」const buffer = new ArrayBuffer(32);buffer.byteLength; // 32const v = new Int32Array(buffer);ArrayBuffer.isView(v) // trueconst buffer2 = buffer.slice(0, 1);上面代码表示实例对象 buffer 占用 32 个字节。它有实例属性?byteLength?,表示当前实例占用的内存字节长度。它拥有一个静态方法isView(),这个方法可以用来判断是否为TypedArray实例或DataView实例。它拥有实例方法?slice(),用来复制一部分内存,使用方式同数组的slice方法。除了slice方法,ArrayBuffer对象不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。二、视图2.1 TypedArray? ? ?TypedArray一共包含九种类型,每一种都是一个构造函数。(DataView视图不支持Uint8ClampedArray,其他都支持)名称描述长度(字节)Int8Array8位有符号整数1Uint8Array8位无符号整数1Uint8ClampedArray8位无符号整型固定数组(数值在0~255之间)1Int16Array16位有符号整数2Uint16Array16位无符号整数2Int32Array32位有符号整数4Uint32Array32 位无符号整数4Float32Array32 位 IEEE 浮点数4Float64Array64 位 IEEE 浮点数8每一种视图都有一个BYTES_PER_ELEMENT常数,表示这种数据类型占据的字节数。Int8Array.BYTES_PER_ELEMENT // 1Uint8Array.BYTES_PER_ELEMENT // 1Int16Array.BYTES_PER_ELEMENT // 2Uint16Array.BYTES_PER_ELEMENT // 2Int32Array.BYTES_PER_ELEMENT // 4Uint32Array.BYTES_PER_ELEMENT // 4Float32Array.BYTES_PER_ELEMENT // 4Float64Array.BYTES_PER_ELEMENT // 8??这 9 个构造函数生成的数组,统称为TypedArray视图。它们很像普通数组,都有length属性,普通数组的操作方法和属性,对TypedArray 数组完全适用。?普通数组与 TypedArray 数组的差异主要在以下方面:TypedArray和Array之间也可以互相转换const typedArray = new Uint8Array([1, 2, 3, 4]);const normalArray = Array.apply([], typedArray);「建立TypedArray视图」// 创建一个8字节的ArrayBufferconst a = new ArrayBuffer(8);// 创建一个指向a的Int32视图,开始于字节0,直到缓冲区的末尾const a1 = new Int32Array(a);// 创建一个指向a的Uint8视图,开始于字节4,直到缓冲区的末尾const a2 = new Uint8Array(a, 4);// 创建一个指向a的Int16视图,开始于字节4,长度为2const a3 = new Int16Array(a, 4, 2);上面代码在一段长度为 8 个字节的内存(a)之上,生成了三个视图:a1、a2和a3。视图的构造函数可以接受三个参数:第一个参数(必选):视图对应的底层ArrayBuffer对象;第二个参数:视图开始的字节序号,默认从 0 开始;第三个参数:视图包含的数据个数,默认直到本段内存区域结束;??建立了视图以后,就可以进行各种操作了。这里需要明确的是,视图其实就是普通数组,语法完全没有什么不同,只不过它直接针对内存进行操作,而且每个成员都有确定的数据类型。所以,视图就被叫做“类型化数组”。「TypedArray视图操作」const buffer = new ArrayBuffer(8);const int16View = new Int16Array(buffer);for (let i = 0; i [778, 2059]如果一段数据是大端字节序(大端字节序主要用于数据传输),TypedArray 数组将无法正确解析,因为它只能处理小端字节序!为了解决这个问题,JavaScript 引入DataView对象,可以设定字节序。2.2 DataView? ? ?DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。? 字节顺序,又称端序或尾序(英语:Endianness),在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字的字节的排列顺序。字节的排列方式有两个通用规则。例如,一个多位的整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)在最高有效字节的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。例如假设上述变量 x 类型为int,位于地址 0x100 处,它的值为 0x01234567,地址范围为 0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,..。而小端法将是:0x100: 67, 0x101: 45,..。?「语法」new DataView(buffer [, byteOffset [, byteLength]])相关的参数说明如下:buffer:ArrayBuffer 对象 或 SharedArrayBuffer 对象;byteOffset(可选):此 DataView 对象的第一个字节在 buffer 中的字节偏移。如果未指定,则默认从第一个字节开始;异常:此 DataView 对象的字节长度。如果未指定,这个视图的长度将匹配 buffer 的长度;「示例」const buffer = new ArrayBuffer(16);const view = new DataView(buffer, 0);view.setInt8(1, 68);view.getInt8(1); // 68??如果一次操作(get或者set)两个或两个以上字节,就必须明确数据的存储方式,到底是小端字节序还是大端字节序。DataView的操作方法默认使用大端字节序解读数据,如果需要使用小端字节序解读,必须在操作方法中指定参数为true(get方法的第二个参数和set方法的第三个参数)。const buffer = new ArrayBuffer(24);const dv = new DataView(buffer);// 1个字节,默认大端字节序const v1 = dv.getUint8(0);// 小端字节序const v1 = dv.getUint16(1, true);// 大端字节序const v2 = dv.getUint16(3, false);// 在第5个字节,以小端字节序写入值为11的32位整数dv.setInt32(4, 11, true);??对于直接处理ArrayBuffer对象的业务场景不是特别多,特别是写页面比较多的同学。笔者深刻认识并运用的场景,主要是在处理比较复杂且数据量比较大的点云数据,前端接收到的点云数据已经是原始采集数据转换过的二进制数据,前端需要对二进制数据进行解析,运用的解析方法就是上述提到的各种方法。下面介绍一下业务场景中比较常见到的一种二进制表示类型——Blob。三、Blob3.1 基本介绍??Blob 对象比较常用于文件上传、文件读写操作等。在对文件读写的时候,我们更多的时候只是操作File对象,而File继承了所有Blob的属性。所以在我们看来,File对象可以看作一种特殊的Blob对象。??而Blob 对象与 ArrayBuffer 的区别在于,Blob 对象用于操作二进制文件, ArrayBuffer 用于直接操作内存,所以他们有如下图的关系:「语法」const blob = new Blob(array [, options]);相关的参数说明如下:array:字符串或二进制对象,表示新生成的Blob实例对象的内容;options(可选):比较常用的属性 type,表示数据的 MIME 类型,默认空字符串;「示例」const array = [' Hello World! '];const blob = new Blob(array, {type : 'text/html'});「属性和方法」由上图可以看到,Blob对象拥有size和type两个属性,以及多种自有方法。比较常用的方法slice、arrayBuffer等;slice方法主要用来拷贝原来的数据,返回的也是一个Blob实例,这个方法可以用来做切片上传。arrayBuffer方法返回一个 Promise 对象,包含 blob 中的数据,并在 ArrayBuffer 中以二进制数据的形式呈现。const blob = new Blob([]);blob.slice(0, 1);blob.arrayBuffer().then(buffer => /* 处理 ArrayBuffer 数据的代码……*/);3.2 运用场景通过window.URL.createObjectURL方法可以把一个blob转化为一个Blob URL,并且用做文件下载或者图片显示的链接。Blob URL所实现的下载或者显示等功能,仅仅可以在单个浏览器内部进行。而不能在服务器上进行存储,亦或者说它没有在服务器端存储的意义。下面是一个Blob的例子,可以看到它很短blob:d3958f5c-0777-0845-9dcf-2cb28783acaf和冗长的Base64格式的Data URL相比,Blob URL的长度显然不能够存储足够的信息,这也就意味着它只是类似于一个浏览器内部的“引用“。从这个角度看,Blob URL是一个浏览器自行制定的一个伪协议。「文件下载」??「图片显示」??「切片上传」??「本地文件读取」?四、参考资料《了解 ES6 TypedArray 和 DataView》《聊聊JS的二进制家族:Blob、ArrayBuffer和Buffer》《ECMAScript 6 入门》? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?招募优秀的你加入??:?Vol.367?????
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-11 13:00 , Processed in 0.445956 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表