JavaScript教程(十四)--- 类型化数组

JavaScript 类型化数组

JavaScript 类型化数组是一种类似数组的对象,并提供了一种用于在内存缓冲中访问原始二进制数据的机制。

引入类型化数组并非是为了取代 JavaScript 中数组的任何一种功能。相反,它为开发者提供了一个操作二进制数据的接口。这在操作与平台相关的特性时会很有用,例如:音频视频编辑和访问 WebSocket 原始数据等。JavaScript 类型化数组中的每一个元素都是以某种格式表示的原始二进制值,JavaScript 支持从 8 位整数到 64 位浮点数的多种二进制格式。

类型化数组拥有许多与数组相同的方法,语义也相似。但是,类型化数组并不是普通数组,因为在类型化数组上调用 Array.isArray() 会返回 false。此外,并不是所有可用于普通数组的方法都能被类型化数组所支持(如 push 和 pop)。

为了最大程度的灵活性和效率,JavaScript 将类型化数组的实现拆分为缓冲视图两部分。缓冲是一种表示了数据块的对象,它没有格式可言,也没有提供访问其内容的机制。为了访问缓冲中的内存,你需要使用视图。视图提供了上下文——即数据类型、起始偏移量和元素数量。

同一缓冲上的不同类型化数组。每种类型化数组的元素数量与元素宽度都不同。

缓冲

有两种类型的缓冲:ArrayBuffer 和 SharedArrayBuffer。它们都是内存块的低级表示。它们名字中都含有“array”,但是它们与数组并没有太多关系——你不能直接读写它们。相反,缓冲是通用的对象,它们只包含原始数据。为了访问缓冲所表示的内存,你需要使用视图。

缓冲支持以下操作:

  • 分配:创建一个新的缓冲时,会分配一个新的内存块,并且初始化为 0
  • 复制:使用 slice() 方法,你可以高效地复制缓冲中的一部分数据,而不需要创建视图来手动复制每一个字节。
  • 转移:使用 transfer() 和 transferToFixedLength() 方法,可以将内存块的所有权转移给一个新的缓冲对象。若你想要在不同的执行上下文间转移数据,又不想复制,这些方法就很有用。转移后,原始缓冲将不再可用。SharedArrayBuffer 不能被转移(因为缓冲已经被所有执行上下文共享)。
  • 调整大小:使用 resize() 方法,可以调整内存块的大小(只要不超过预设的 maxByteLength 限制)。SharedArrayBuffer 只能增长,不能缩小。

ArrayBuffer 与 SharedArrayBuffer 之间的区别是,前者在同一时刻只能所属于单个执行上下文。如果你将 ArrayBuffer 传递给另一个执行上下文,它会被转移,原始的 ArrayBuffer 将不再可用。这确保了同一时刻只有一个执行上下文可以访问内存。SharedArrayBuffer 在传递给另一个执行上下文时不会被转移,因此可以被多个执行上下文同时访问。当多个线程同时访问同一内存块时,可能会出现竞争条件,这时候 Atomics 方法就很有用了。

视图

目前主要有两种视图:类型化数组视图和 DataView。类型化数组提供了实用方法,可以方便地转换二进制数据。DataView 更底层,可以精确控制数据的访问方式。使用这两种视图读写数据的方式是非常不同的。

两种视图都会使 ArrayBuffer.isView() 返回 true。它们都有以下属性:

‘buffer’视图所引用的底层缓冲。

‘byteOffset’视图相对于缓冲起始位置的偏移量(以字节为单位)。

‘byteLength’视图的长度(以字节为单位)。

两者的构造函数都接受上述三个分离的参数。类型化数组构造函数还接受 length 作为元素数量,而不是字节长度。

类型化数组视图

类型化数组视图有自描述的名称,并且提供了所有常见数值类型的视图,如 Int8Uint32 和 Float64 等等。还有一种特殊的类型化数组视图,Uint8ClampedArray,它会将值钳制(clamp)到 0 到 255 之间,这在 Canvas 数据处理等场景中很有用。

类型值范围字节数描述对应的 Web IDL 类型等效的 C 类型
Int8Array-128~12718 位有符号整数(补码)byteint8_t
Uint8Array0~25518 位无符号整数octetuint8_t
Uint8ClampedArray0~25518 位无符号整数(值会被裁剪)octetuint8_t
Int16Array-32768~32767216 位有符号整数(补码)shortint16_t
Uint16Array0~65535216 位无符号整数unsigned shortuint16_t
Int32Array-2147483648~2147483647432 位有符号整数(补码)longint32_t
Uint32Array0~4294967295432 位无符号整数unsigned longuint32_t
Float32Array-3.4E38~3.4E38 以及 1.2E-38(最小正数)432 位 IEEE 浮点数(7 位有效数字,例如 1.123456unrestricted floatfloat
Float64Array-1.8E308~1.8E308 以及 5E-324(最小正数)864 位 IEEE 浮点数(16 位有效数字,例如 1.123...15unrestricted doubledouble
BigInt64Array-263~263 - 1864 位有符号整数(补码)bigintint64_t (signed long long)
BigUint64Array (en-US)0~264 - 1864 位无符号整数bigintuint64_t (unsigned long long)

所有类型化数组类型都有相同的方法与属性,这些方法与属性由 TypedArray 类定义。它们的唯一区别在于底层数据类型和字节数的不同。这在值编码与标准化中有详细讨论。

类型化数组原则上是固定长度的,因此并不存在可以改变数组长度的方法,如 poppushshiftsplice 和 unshift。此外,flat 也不可用,因为类型化数组无法嵌套;concat 和 flatMap 则是由于应用场景不大,亦不可用。由于 splice 不可用,因此 toSpliced 也不可用。所有其他数组方法都是 Array 和 TypedArray 共享的。

另一方面,类型化数组有额外的 set 和 subarray 方法,可以用来优化“同缓冲多视图”的场景。set() 方法允许你使用一个数组或类型化数组的数据,来对另一个类型化数组的多个索引同时进行设置。如果两个类型化数组共享同一个底层缓冲,那么这个操作可能会更加高效,因为这是一个快速的内存移动。subarray() 方法创建一个新的类型化数组视图,它引用与原始类型化数组相同的缓冲,但是范围更窄。

在不改变底层缓冲的前提下,无法直接改变类型化数组的长度。但是,当类型化数组的底层是可调整大小的缓冲,且没有固定的 byteLength 时,它就会跟踪长度,即它的长度会随着缓冲的大小而自动调整。详情请参阅底层为可变大小缓冲时的行为。

类似于普通数组,你可以使用方括号表示法来访问类型化数组的元素。底层缓冲中对应的字节会被解析为一个数字。任何使用数字(或数字的字符串表示,因为数字总是在访问属性时被转换为字符串)的属性访问都会被类型化数组代理——它们永远不会与对象本身交互。这意味着,例如:

  • 超越边界索引的访问总是返回 undefined,而不会实际访问对象上的属性。
  • 任何尝试写入超越边界的属性的行为都不会生效:它既不会抛出错误,也不会改变缓冲或类型化数组。
  • 类型化数组的索引看起来是可配置的和可写的,但是任何改变它们的属性的尝试都会失败。
const uint8 = new Uint8Array([1, 2, 3]);
console.log(uint8[0]); // 1

// 仅用作示例。不要在生产代码中使用。
uint8[-1] = 0;
uint8[2.5] = 0;
uint8[NaN] = 0;
console.log(Object.keys(uint8)); // ["0", "1", "2"]
console.log(uint8[NaN]); // undefined

// 非数字访问仍然有效
uint8[true] = 0;
console.log(uint8[true]); // 0

Object.freeze(uint8); // TypeError:无法冻结非空缓冲的视图

DataView

DataView 是一种底层接口,它提供可以操作缓冲中任意数据的 getter/setter API。这对操作不同类型数据的场景很有帮助,例如:类型化数组视图都是运行在本地字节序模式(参考字节序),可以通过使用 DataView 来控制字节序。默认是大端字节序(big-endian),但可以调用 getter/setter 方法改为小端字节序(little-endian)。

DataView 不需要对齐,多字节读写可以从任意指定的偏移量开始。setter 方法也是如此。

下面的例子使用 DataView 来获取任意数字的二进制表示:

function toBinary(
  x,
  { type = "Float64", littleEndian = false, separator = " ", radix = 16 } = {},
) {
  const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT;
  const dv = new DataView(new ArrayBuffer(bytesNeeded));
  dv[`set${type}`](0, x, littleEndian);
  const bytes = Array.from({ length: bytesNeeded }, (_, i) =>
    dv
      .getUint8(i)
      .toString(radix)
      .padStart(8 / Math.log2(radix), "0"),
  );
  return bytes.join(separator);
}

console.log(toBinary(1.1)); // 3f f1 99 99 99 99 99 9a
console.log(toBinary(1.1, { littleEndian: true })); // 9a 99 99 99 99 99 f1 3f
console.log(toBinary(20, { type: "Int8", radix: 2 })); // 00010100

使用类型化数组的 Web API

下面是一些使用类型化数组的 API 示例,其并不完整,我们会在未来添加更多示例。

FileReader.prototype.readAsArrayBuffer()

FileReader.prototype.readAsArrayBuffer() 读取对应的 Blob 或 File 的内容

XMLHttpRequest.prototype.send()

XMLHttpRequest 实例的 send() 方法现在支持使用类型化数组和 ArrayBuffer 对象作为参数。

ImageData.data

是一个 Uint8ClampedArray 对象,用来描述包含按照 RGBA 序列的颜色数据的一维数组,其值的范围在 0 到 255 之间。

示例

使用视图和缓冲

首先,我们需要创建一个 16 字节固定长度的缓冲:

const buffer = new ArrayBuffer(16);

现在我们有了一段初始化为 0 的内存,目前还做不了什么太多操作。让我们确认一下数据的字节长度:

if (buffer.byteLength === 16) {
  console.log("是 16 字节。");
} else {
  console.log("大小有问题!");
}

在实际开始操作这个缓冲之前,还需要一个视图。现在,我们就创建一个视图,该视图将把缓冲内的数据视为一个 32 位有符号整数数组:

const int32View = new Int32Array(buffer);

现在我们可以像普通数组一样访问该数组中的元素:

for (let i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

该代码会将数组以 024 和 6 填充(一共 4 个 4 字节元素,所以总长度为 16 字节)。

相同数据上的多个视图

更有意思的是,你可以在同一数据上创建多个视图。例如:基于上文的代码,我们可以像这样继续操作:

const int16View = new Int16Array(buffer);

for (let i = 0; i < int16View.length; i++) {
  console.log(`索引 ${i}:${int16View[i]}`);
}

这里我们创建了一个 16 位整数视图,该视图共享上文的 32 位整数视图的缓冲,然后以 16 位整数打印出缓冲里的数据,这次我们会得到 00204060 这样的输出(假设是小端序编码)。

Int16Array  |   0  |  0   |   2  |  0   |   4  |  0   |   6  |  0   |
Int32Array  |      0      |      2      |      4      |      6      |
ArrayBuffer | 00 00 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |

进一步地:

int16View[0] = 32;
console.log(`32 位数组的索引 0 处数据是:${int32View[0]}`);

这次的输出是 "32 位数组的索引 0 处数据是:32"。也就是,这 2 个数组都是同一数据的以不同格式展示出来的视图。

Int16Array  |  32  |  0   |   2  |  0   |   4  |  0   |   6  |  0   |
Int32Array  |     32      |      2      |      4      |      6      |
ArrayBuffer | 00 02 00 00 | 02 00 00 00 | 04 00 00 00 | 06 00 00 00 |

你可以对任意视图类型进行这样的操作,尽管如果你设置一个整数,然后以浮点数的形式读取它,你可能会得到一个奇怪的结果,因为位的解释方式不同。

const float32View = new Float32Array(buffer);
console.log(float32View[0]); // 4.484155085839415e-44

从缓冲读取文本

缓冲并不总是表示数字。例如,读取文件可以给你一个文本数据缓冲。你可以使用类型化数组从缓冲中读取这些数据。

下面的代码使用 TextDecoder web API 读取 UTF-8 文本:

const buffer = new ArrayBuffer(8);
const uint8 = new Uint8Array(buffer);
// 我们手动写入数据,假装它已经在缓冲区中了
uint8.set([228, 189, 160, 229, 165, 189]);
const text = new TextDecoder().decode(uint8);
console.log(text); // "你好"

下面的代码使用 String.fromCharCode() 方法读取 UTF-16 文本:

const buffer = new ArrayBuffer(8);
const uint16 = new Uint16Array(buffer);
// 我们手动写入数据,假装它已经在缓冲区中了
uint16.set([0x4f60, 0x597d]);
const text = String.fromCharCode(...uint16);
console.log(text); // "你好"

使用复杂的数据结构

通过将缓冲与不同类型视图组合,以及修改内存访问的偏移量,你可以操作包含更多更复杂数据结构的数据。例如,你可以操作诸如 WebGL 或数据文件这些复杂的数据结构。

请看如下的 C 语言结构体:

struct someStruct {
  unsigned long id;
  char username[16];
  float amountDue;
};

你可以采用如下代码访问一个包含此类结构体的缓冲:

const buffer = new ArrayBuffer(24);

// …将数据读入缓冲…

const idView = new Uint32Array(buffer, 0, 1);
const usernameView = new Uint8Array(buffer, 4, 16);
const amountDueView = new Float32Array(buffer, 20, 1);

这样一来,你就得到了一个类似的数据结构,例如,amountDueView[0] 对应了 C 的 amountDue 字段。

备注:C 语言结构体的数据对齐与平台相关。因此需要防范和考虑不同平台字节填充对齐的差异。

转换为普通数组

在处理完一个类型化数组后,有时需要把它转为普通数组,以便可以从 Array 原型中受益。可以通过使用 Array.from() 实现:

const typedArray = new Uint8Array([1, 2, 3, 4]);
const normalArray = Array.from(typedArray);

也可以使用展开语法。

const typedArray = new Uint8Array([1, 2, 3, 4]);
const normalArray = [...typedArray];

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/543927.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SAP软件如何批量修改物料主数据

在SAP/ERP系统日常运维中经常会遇到批量修改物料主数据的业务需求&#xff0c; 遇到这种业务需求可以使用SAP提供的标准的事务代码MM17进行处理。 下面按业务场景介绍下具体的操作步骤 业务场景1 需要将一批物料主数据的采购组字段全部修改为002。 具体操作步骤如下&#…

[漏洞复现]D-Link未授权RCE漏洞复现(CVE-2024-3273)

声明&#xff1a;亲爱的读者&#xff0c;我们诚挚地提醒您&#xff0c;Aniya网络安全的技术文章仅供个人研究学习参考。任何因传播或利用本实验室提供的信息而造成的直接或间接后果及损失&#xff0c;均由使用者自行承担责任。Aniya网络安全及作者对此概不负责。如有侵权&#…

Spring+SpringMVC的知识总结

一:技术体系架构二:SpringFramework介绍三:Spring loC容器和核心概念3.1 组件和组件管理的概念3.1.1什么是组件:3.1.2:我们的期待3.1.3Spring充当组件管理角色(IOC)3.1.4 Spring优势3.2 Spring Ioc容器和容器实现3.2.1普通和复杂容器3.2.2 SpringIOC的容器介绍3.2.3 Spring IOC…

L1-027 出租

下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救声&#xff0c;急问这个怎么破。其实这段代码很简单&#xff0c;index数组就是arr数组的下标&#xff0c;index[0]2 对应 arr[2]1&#xff0c;index[1]0 对应 arr[0]8&#xff0c;index[2]3 对应 arr[3]0&…

30、链表-两两交换链表

思路&#xff1a; 放入集合中两两交换&#xff0c;然后再重新构建链表可以解决。但是不是最优方案 第二种方式如下&#xff1a; 代码如下&#xff1a; public ListNode swapPairs(ListNode head) {if (headnull||head.nextnull){return head;}ListNode dummy new ListNode(…

麒麟服务器操作系统安装DHCP服务02

原文链接&#xff1a;麒麟服务器操作系统安装DHCP服务02 Hello&#xff0c;大家好啊&#xff01;继昨天介绍了在麒麟服务器操作系统上部署DHCP服务并演示了终端自动获取IP地址的过程之后&#xff0c;今天我们将进一步探讨如何通过绑定终端的MAC地址来为其分配固定的IP地址。这种…

第四十八周:文献阅读

目录 摘要 Abstract 文献阅读&#xff1a;时间序列预测的傅里叶图卷积网络 现有问题 提出方法 方法论 傅里叶级数 图信号的傅里叶变换 论文方法&#xff1a;F-GCN&#xff08;傅立叶图卷积网络&#xff09; 数据构建 傅立叶嵌入模块 时空ChebyNet层 框架伪代码 研…

七、Yocto使用systemd设置开机自启动程序

文章目录 Yocto使用systemd设置开机自启动程序一、 systemd介绍及service设置二、yocto集成 Yocto使用systemd设置开机自启动程序 本篇文章为基于raspberrypi 4B单板的yocto实战系列的第七篇文章&#xff1a; 一、yocto 编译raspberrypi 4B并启动 二、yocto 集成ros2(基于rasp…

人工智能轨道交通行业周刊-第77期(2024.4.1-4.14)

本期关键词&#xff1a;货车巡检机器人、铁路安全技防、车辆换长、阿里千问、大模型创业 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…

宝妈如何在家创造收入?五种兼职工作让你轻松赚钱!

许多宝妈为了陪伴孩子成长&#xff0c;毅然选择了全职妈妈的角色&#xff0c;然而&#xff0c;她们内心仍希望能有一份收入&#xff0c;实现经济独立。于是&#xff0c;寻找既能照顾家庭又能赚钱的工作成了她们的迫切需求。 然而&#xff0c;这样的需求也往往让宝妈们成为一些…

鸿蒙语言TypeScript学习第16天:【类】

1、TypeScript 类 TypeScript 是面向对象的 JavaScript。 类描述了所创建的对象共同的属性和方法。 TypeScript 支持面向对象的所有特性&#xff0c;比如 类、接口等。 TypeScript 类定义方式如下&#xff1a; class class_name { // 类作用域 }定义类的关键字为 class&am…

《自动机理论、语言和计算导论》阅读笔记:p139-p171

《自动机理论、语言和计算导论》学习第 7 天&#xff0c;p139-p171总结&#xff0c;总计 33 页。 一、技术总结 1.reversal p139, The reversal of a string a1a2…an is the string written backwards, that is anan-1…a1. 2.homomorphism A string homomorphism is a f…

原理图添加封装

双击器件选中》右边弹出Properties》点击添加封装Footprint 找到你想要的封装&#xff0c;确定即可。

算法设计与分析实验报告c++实现(生命游戏、带锁的门、三壶谜题、串匹配问题、交替放置的碟子)

一、实验目的 1&#xff0e;加深学生对分治法算法设计方法的基本思想、基本步骤、基本方法的理解与掌握&#xff1b; 2&#xff0e;提高学生利用课堂所学知识解决实际问题的能力&#xff1b; 3&#xff0e;提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 1、 编…

Java多线程学习总结

在Java中&#xff0c;多线程编程是一种重要的编程模型&#xff0c;它允许程序同时执行多个任务&#xff0c;从而提高了程序的执行效率和响应速度。 一、基本概念 进程与线程&#xff1a;进程是系统分配资源的基本单位&#xff0c;它包含了程序执行所需的资源&#xff0c;如代码…

Django第三方功能的使用

Django第三方功能的使用 Django REST framework前言1、Django--Restframework--coreapi版文档BUG:AssertionError: coreapi must be installed for schema support.How to run Django with Uvicorn webserver?2、序列化类 Serializer的使用模型序列化类 ModelSerializer的使用…

2024年阿里云优惠券领取攻略

阿里云作为国内领先的云计算服务提供商&#xff0c;以其稳定、高效、安全的服务赢得了众多用户的青睐。为了吸引用户上云&#xff0c;阿里云经常推出各种优惠活动&#xff0c;其中就包括阿里云优惠券。本文将为大家详细解读2024年阿里云优惠券的领券攻略&#xff0c;帮助大家轻…

DDR3的使用(非AXI4总线)

参考小梅哥视频&#xff1a;https://www.bilibili.com/video/BV1va411c7Dz/?p48&spm_id_frompageDriver&vd_sourceaedd69dc9740e91cdd85c0dfaf25304b 一、DDR3的MIG配置 找到MIG的IP核 AXI4 interface不用勾选 不需要兼容以下的FPGA就不用勾选 选择DDR3 1.1 三…

ARM v8 Cortex R52内核 04 时钟和复位 Clocking and Resets

ARM v8 Cortex R52内核 04 时钟和复位 Clocking and Resets 4.1 Clock and clock enables 时钟和时钟使能 Cortex-R52处理器具有一个单一的时钟&#xff0c;驱动着所有的触发器和RAM。各种输入&#xff0c;包括复位输入&#xff0c;都有同步逻辑使它们可以与处理器时钟异步操…

C语言—实现循序表的增删查改

一.正文 嗨嗨嗨&#xff01;大家好&#xff01;今天我为大家分享的是数据结构知识——顺序表。废话不多数&#xff0c;让我们开始今天的知识分享吧。 二.正文 1.1认识数据结构 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素…