JavaScript里面的二进制

概述

最近在做IOT设备配网开发的时候,处理了很多跟二进制、字节相关的事情,总结了一下JavaScript中有关二进制方面的一些知识点。

二进制和字节

首先,现代计算机是基于二进制的,从现代计算机电路来说,只有高电平/低电平两种状态,即为0/1状态,计算机中所有的数据按照具体的编码格式以二进制的形式存储在设备中。

计算机通信和存储的时候都是以0101这样的二进制数据为基础来做处理的,这儿的一个0和1占的地方就叫bit(位),即一个二进制位。可以看出位(bit)是长度单位。8位组成一个字节,所以字节(Byte)也是长度单位。

位和字节的换算关系如下:

1Byte=8bit

1KB=1024B

1MB=1024KB(2的十次方)

二进制的计算

二进制数据的计算指的是位数据的计算,也就是位运算。

位运算分为以下几种操作:

符号描述运算规则
&两个位都为1时,结果才为1


^异或两个位相同为0,相异为1
~取反0变1,1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,正数左补0,负数左补1,右边丢弃。

注意:负数按补码形式参加按位与运算。

原码:用最高位表示符号位,其余位表示数值位的编码称为原码。其中,正数的符号位为 0,负数的符号位为 1。正数的原码、反码、补码均相同。负数的反码:原码的符号位保持不变,其余位逐位取反,即可得原码的反码。
负数的补码:在反码的基础上加 1 即得该原码的补码。例如:
+11 的原码为: 0000 1011
+11 的反码为: 0000 1011
+11 的补码为: 0000 1011-7 的原码为:1000 0111
-7 的反码为:1111 1000
-7 的补码为:1111 1001

位运算的应用很多,这里讲一个经典的,交换两个数。

通常交换两个数的做法如下,比如交换a和b:

let temp = a;
a = b;
b = temp;

如果我们用位运算来做

a ^= b;
b ^= a;
a ^= b;

使用位运算可以少定义一个变量temp,节省一个点内存空间;

字节顺序

字节顺序涉及到二进制数据在内存中怎么存储和网络数据的传输,假设我们定义一个变量 let value = 0x12345678,它在内存中是怎么存储的?

前面我们说过计算机里存储数据都是以二进制的形式存储的,假设一个整型占4个字节,那么先将它转成二进制:

parseInt(12).toString(2)00010010 00110100 01010110 01111000。

按照正常阅读习惯,我们认为它在计算机内部的存储格式为:

低地址
buf[0] (0x12) -- 高位字节
buf[1] (0x34)
buf[2] (0x56)
buf[3] (0x78) -- 低位字节
高地址

这种存储模式叫大端模式。相对的还有小端模式

大端和小端

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。

  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。

Little-Endian: 低地址存放低位,如下:

低地址
buf[0] (0x78) -- 低位字节
buf[1] (0x56)
buf[2] (0x34)
buf[3] (0x12) -- 高位字节
高地址

网络传输一般采用大端序,也被称之为网络字节序。

计算机内部的字节存储序列叫本机序,不同CPU会有不同,摘自维基百科上的一段说明:

x86、MOS Technology 6502、Z80、VAX、PDP-11等处理器为小端序;
Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除V9外)等处理器为大端序;
ARM、PowerPC(除PowerPC 970外)、DEC Alpha、SPARC V9、MIPS、PA-RISC及IA64的字节序是可配置的。

总结:采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。到目前为止,采用大端或者小端进行数据存放,其孰优孰劣也没有定论。

  • 以上都是二进制相关的基础知识,下面讲下JavaScript中的二进制处理

创建二进制数据

基本的二进制对象是 ArrayBuffer —— 对固定长度的连续内存空间的引用。

let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 buffer
console.log(buffer.byteLength); // 16

它会分配一个 16 字节的连续内存空间,并用 0 进行预填充

注意:ArrayBuffer 不是某种东西的数组 让我们先澄清一个可能的误区。ArrayBufferArray 没有任何共同之处:

  • 它的长度是固定的,我们无法增加或减少它的长度。

  • 要访问单个字节,需要另一个“视图”对象(TypedArray,下面讲),而不是 buffer[index]

ArrayBuffer 是一个内存区域。它里面存储了什么?无从判断。只是一个原始的字节序列。如要操作 ArrayBuffer,我们需要使用“视图”对象。

视图对象

视图对象本身并不存储任何东西。它是一副“眼镜”,透过它来读写存储在 ArrayBuffer 中的字节。

例如:

  • Uint8Array —— 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位,2的8次方是256,因此只能容纳那么多)。这称为 “8 位无符号整数”。

  • Uint16Array —— 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。

  • Uint32Array —— 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。

  • Float64Array —— 将每 8 个字节视为一个 5.0x10-3241.8x10308 之间的浮点数。

因此,一个 16 字节 ArrayBuffer 中的二进制数据可以解释为 16 个“小数字”,或 8 个更大的数字(每个数字 2 个字节),或 4 个更大的数字(每个数字 4 个字节),或 2 个高精度的浮点数(每个数字 8 个字节)。

使用视图操作二进制数据

ArrayBuffer 是原始的二进制数据。

但是,如果我们要操作它,我们必须使用视图(view),例如:

let buffer = new ArrayBuffer(16); // 创建一个长度为 16 的 bufferlet view = new Uint32Array(buffer); // 将 buffer 视为一个 32 位整数的序列console.log(Uint32Array.BYTES_PER_ELEMENT); // 每个整数 4 个字节console.log(view.length); // 4,它存储了 4 个整数
console.log(view.byteLength); // 16,字节中的大小// 让我们写入一个值
view[0] = 123456;// 遍历值
for(let num of view) {console.log(num); // 123456,然后 0,0,0(一共 4 个值)
}

TypedArray

所有这些视图(Uint8ArrayUint32Array 等)的通用术语是 TypedArray。它们共享同一方法和属性集。

请注意,没有名为 TypedArray 的构造器,它只是表示 ArrayBuffer 上的视图的通用总称术语,包Int8ArrayUint8Array

当你看到 new TypedArray 之类的内容时,它表示 new Int8Arraynew Uint8Array 等。

TypedArray的行为类似于常规数组:具有索引,并且是可迭代的。

一个TypedArray的构造器(无论是 Int8ArrayFloat64Array),其行为各不相同,并且取决于参数类型。

参数有 5 种变体:

new TypedArray(buffer, [byteOffset], [length]);
new TypedArray(object);
new TypedArray(typedArray);
new TypedArray(length);
new TypedArray();`
  1. 如果给定的是 ArrayBuffer 参数,则会在其上创建视图。前面我们已经用过该语法了。

  • buffer —— 底层的 ArrayBuffer

  • byteOffset —— 视图的起始字节位置(默认为 0)也就是要暴露的第一个字节的索引。

  • byteLength —— 视图的字节长度(默认至 buffer 的末尾)要暴露的字节数。默认值: arrayBuffer.byteLength - byteOffset

如果给定的是 Array或任何类数组对象,则会创建一个相同长度的类型化数组,并复制其内容。

我们可以使用它来预填充数组的数据:

let arr = new Uint8Array([0, 1, 2, 3]);
console.log( arr.length ); // 4,创建了相同长度的二进制数组
console.log( arr[1] ); // 1,用给定值填充了 4 个字节(无符号 8 位整数)`

如果给定的是另一个 TypedArray,也是如此:创建一个相同长度的类型化数组,并复制其内容。如果需要的话,数据在此过程中会被转换为新的类型。

let arr16 = new Uint16Array([1, 1000]);let arr8 = new Uint8Array(arr16);console.log( arr8[0] ); // 1console.log( arr8[1] ); // 232,试图复制 1000,但无法将 1000 放进 8 位字节中(详述见下文)。

对于数字参数 length —— 创建类型化数组以包含这么多元素。它的字节长度将是 length 乘以单个 TypedArray.BYTES_PER_ELEMENT 中的字节数:

let arr = new Uint16Array(4); // 为 4 个整数创建类型化数组 console.log( Uint16Array.BYTES_PER_ELEMENT ); // 每个整数 2 个字节 console.log( arr.byteLength ); // 8(字节中的大小)

不带参数的情况下,创建长度为零的类型化数组。

我们可以直接创建一个 TypedArray,而无需提及 ArrayBuffer。但是,视图离不开底层的 ArrayBuffer,因此,除第一种情况(已提供 ArrayBuffer)外,其他所有情况都会自动创建 ArrayBuffer

如要访问底层的 ArrayBuffer,那么在 TypedArray 中有如下的属性:

  • arr.buffer —— 引用 ArrayBuffer

  • arr.byteLength —— ArrayBuffer 的长度。

因此,我们总是可以从一个视图转到另一个视图:

let arr8 = new Uint8Array([0, 1, 2, 3]);// 同一数据的另一个视图
let arr16 = new Uint16Array(arr8.buffer);

下面是类型化数组的列表:

  • Uint8ArrayUint16ArrayUint32Array —— 用于 8、16 和 32 位的整数。

  • Uint8ClampedArray —— 用于 8 位整数,在赋值时便“固定“其值(见下文)。

  • Int8ArrayInt16ArrayInt32Array —— 用于有符号整数(可以为负数)。

  • Float32ArrayFloat64Array —— 用于 32 位和 64 位的有符号浮点数。

越界行为

如果我们尝试将越界值写入类型化数组会出现什么情况?不会报错。但是多余的位被切除。

例如,我们尝试将 256 放入 Uint8Array。256 的二进制格式是 100000000(9 位),但 Uint8Array 每个值只有 8 位,因此可用范围为 0 到 255。

对于更大的数字,仅存储最右边的(低位有效)8 位,其余部分被切除:

因此结果是 0。

257 的二进制格式是 100000001(9 位),最右边的 8 位会被存储,因此数组中会有 1

换句话说,该数字对 28 取模的结果被保存了下来。示例如下

let uint8array = new Uint8Array(16);let num = 256;
alert(num.toString(2)); // 100000000(二进制表示)uint8array[0] = 256;
uint8array[1] = 257;console.log(uint8array[0]); // 0
console.log(uint8array[1]); // 1

Uint8ClampedArray 在这方面比较特殊,它的表现不太一样。对于大于 255 的任何数字,它将保存为 255,对于任何负数,它将保存为 0。此行为对于图像处理很有用。

DataView

**DataView**视图是一个可以从ArrayBuffer对象中读写多种数值类型的底层接口,在读写时不用考虑平台字节序问题。

  • 对于类型化的数组,构造器决定了其格式。整个数组应该是统一的。第 i 个数字是 arr[i]

  • 通过 DataView,我们可以使用 .getUint8(i).getUint16(i) 之类的方法访问数据。我们在调用方法时选择格式,而不是在构造的时候。

语法:

new DataView(buffer, [byteOffset], [byteLength])
  • buffer —— 底层的 ArrayBuffer。与类型化数组不同,DataView 不会自行创建缓冲区(buffer)。我们需要事先准备好。

  • byteOffset —— 视图的起始字节位置(默认为 0)也就是要暴露的第一个字节的索引。

  • byteLength —— 视图的字节长度(默认至 buffer 的末尾)要暴露的字节数。默认值: arrayBuffer.byteLength - byteOffset

例如,这里我们从同一个 buffer 中提取不同格式的数字:

// 4 个字节的二进制数组,每个都是最大值 255
let buffer = new Uint8Array([1, 2, 3, 4]).buffer;let dataView = new DataView(buffer);// 在偏移量为 0 处获取 8 位数字
alert( dataView.getUint8(0) ); // 1// 现在在偏移量为 0 处获取 16 位数字,它由 2 个字节组成,一起解析为 65535
alert( dataView.getUint16(0) ); // 258(最大的 16 位无符号整数)// 在偏移量为 0 处获取 32 位数字
alert( dataView.getUint32(0) ); // 16909060(最大的 32 位无符号整数)dataView.setUint32(0, 0); // 将 4 个字节的数字设为 0,即将所有字节都设为 0

当我们将混合格式的数据存储在同一缓冲区(buffer)中时,DataView 非常有用。例如,当我们存储一个成对序列(16 位整数,32 位浮点数)时,用 DataView 可以轻松访问它们。

2. 总结

ArrayBuffer 是核心对象,是对固定长度的连续内存区域的引用。

几乎任何对 ArrayBuffer 的操作,都需要一个视图。

  1. 它可以是 TypedArray

  • Uint8ArrayUint16ArrayUint32Array —— 用于 8 位、16 位和 32 位无符号整数。

  • Int8ArrayInt16ArrayInt32Array —— 用于有符号整数(可以为负数)。

  • Float32ArrayFloat64Array —— 用于 32 位和 64 位的有符号浮点数。

DataView —— 使用方法来指定格式的视图,例如,getUint8(offset)

在大多数情况下,我们直接对类型化数组进行创建和操作,而将 ArrayBuffer 作为“共同之处(common denominator)”隐藏起来。我们可以通过 .buffer 来访问它,并在需要时创建另一个视图。

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

75230a2f64cbdfeceda78ebf5fc45062.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据在内存中的存储——练习3

题目&#xff1a; 3.1 #include <stdio.h> int main() {char a -128;printf("%u\n",a);return 0; }3.2 #include <stdio.h> int main() {char a 128;printf("%u\n",a);return 0; }思路分析&#xff1a; 首先二者极其相似%u是无符号格式进行…

【Linux】—— 在Linux上进行读写文件操作

前言&#xff1a; 在之前&#xff0c;我已经对进程的相关知识进行了详细的介绍。本期开始&#xff0c;我们将要学习的是关于 “基础I/O”的知识&#xff01;&#xff01;&#xff01; 目录 &#xff08;一&#xff09;C文件接口 &#xff08;二&#xff09;系统文件I/O 1、接…

WebDAV之π-Disk派盘 + BubbleUPnP

BubbleUPnP是一款功能强大的Android播放器,支持UPnP/DLNA多屏互动。它可以将手机内容投屏到电视大屏上,与家人和朋友一起共享。此外,BubbleUPnP还提供了丰富的音乐和影视资源,您可以在线搜索并播放喜欢的内容。 以下是BubbleUPnP的一些主要特点: 1. 支持Chromecast和转码…

WebGL 绘制矩形

上一节绘制了圆点&#xff0c;调用的绘制方法如下&#xff1a;gl.drawArrays(gl.POINTS, 0, 1); 第一个参数明显是个枚举类型&#xff0c;肯定还有其他值&#xff0c;如下所示&#xff1a; POINTS 可视的点LINES 单独线段LINE_STRIP 线条LINE_LOOP 闭合线条TRIANGLES 单独三…

【题解】2596. 检查骑士巡视方案

题解&#xff1a; class Solution {int n,m;bool st[100][100];int flag;int dx[8]{-1,-2,-2,-1,1,2,2,1};int dy[8]{-2,-1,1,2,2,1,-1,-2}; public:bool checkValidGrid(vector<vector<int>>& grid) {m grid.size();n grid[0].size();dfs(grid,0,0,0);ret…

vue3中的吸顶导航交互实现 | VueUse插件

目的&#xff1a;浏览器上下滚动时&#xff0c;若距离顶部的滚动距离大于78px&#xff0c;吸顶导航显示&#xff0c;小于78px隐藏。使用vueuse插件中的useScroll方法​​​​​​​和动态类名控制进行实现 1. 安装 npm i vueuse/core 2. 获得滚动距离 项目中导入&#xff0…

在python程序中用windows的icon

这个exe的弹窗功能会使用到一个ico文件&#xff0c;如图&#xff1a; 用软件GreenfishIconEditorProPortable或者使用在线软件将你需要的图片制作成windows的icon 用程序将ico文件生成文本文件 import base64picture_name "logo.ico" open_pic open("%s…

【100天精通Python】Day56:Python 数据分析_Pandas数据清洗和处理(删除填充插值,数据类型转换,去重,连接与合并)

目录 数据清洗和处理 1.处理缺失值 1.1 删除缺失值&#xff1a; 1.2 填充缺失值&#xff1a; 1.3 插值&#xff1a; 2 数据类型转换 2.1 数据类型转换 2.2 日期和时间的转换&#xff1a; 2.3 分类数据的转换&#xff1a; 2.4 自定义数据类型的转换&#xff1a; 3 数…

神经反馈设备使用感受2:采集Muse的EEG原始数据(转自知乎)

神经反馈设备使用感受2&#xff1a;采集Muse的EEG原始数据 转自知乎&#xff0c;内容很好&#xff0c;怕之后找不到 想了一下&#xff0c;单写一部分来介绍一下Muse在数据采集方面的操作。同时也解释一下我自己的EEG数据是从哪里采集的。 关于Muse EEG数据的精度&#xff0c;在…

vue2实现自定义主题webpack-theme-color-replacer

需求&#xff1a;根据element的自定义主题色&#xff0c;之后改变element的全局所有颜色&#xff0c;解决页面刷新后主题色失效问题&#xff0c;这个需要把颜色存入到浏览器的存储中&#xff0c;如果换个浏览器就得重新选择了哈&#xff0c;如果需要在不同的浏览器保持一致的主…

将AI融入到SEO中—基于Python的实现思路

在当今数字化时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;对于网站和在线业务的成功至关重要。然而&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;我们可以利用它来提升SEO策略并取得更好的效果。本文将介绍如何通过使用Python编…

分布式系统第五讲:分布式事务及实现方案

分布式系统第五讲&#xff1a;分布式事务及实现方案 事务是一个程序执行单元&#xff0c;里面的所有操作要么全部执行成功&#xff0c;要么全部执行失败。而分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。本…

【LeetCode: 1462. 课程表 IV:拓扑排序+图+广度优先搜索】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

现场直击 | 国台国标·中秋礼酒惊艳闪耀酒博会

以酒为媒&#xff0c;以酒会友。 9月9日&#xff0c;以“展示全球佳酿&#xff0c;促进开放合作”为主题的第12届中国&#xff08;贵州&#xff09;国际酒类博览会&#xff08;以下简称“贵州酒博会”&#xff09;在贵阳国际会议展览中心重磅开幕&#xff0c;本届贵州酒博会吸…

狼的传说小游戏

欢迎来到程序小院 狼的传说 玩法&#xff1a; 鼠标左键选择能防御、战斧、风暴3%、滚石10%、藤曼5%、冰柱5%、飞跃10%、三叶草20%、钢叉15%&#xff0c;消灭所有敌人&#xff0c;不同关卡不同敌人&#xff0c;快去闯关消灭敌人吧^^。开始游戏https://www.ormcc.com/play/gameS…

MySQL——读写分离

简介 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作&#xff08;INSERT、UPDATE、DELETE&#xff09;&#xff0c;而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。一般来说都是通过 主从复制&#xff…

得帆云“智改数转,非同帆响”-AIGC+低代码PaaS平台系列白皮书,正式发布!

5月16日下午&#xff0c;由上海得帆信息技术有限公司编写&#xff0c;上海市工业互联网协会指导的以“智改数转&#xff0c;非同帆响”为主题的《得帆云 AIGC低代码PaaS平台系列白皮书》正式在徐汇西岸国际人工智能中心发布。 本次发布会受到了上海市徐汇区政府、各大媒体和业内…

如何实现一个数据库的 UDF?图数据库 NebulaGraph UDF 功能背后的设计与思考

大家好&#xff0c;我是来自 BOSS直聘的赵俊南&#xff0c;主要负责安全方面的图存储相关工作。作为一个从 v1.x 用到 v3.x 版本的忠实用户&#xff0c;在见证 NebulaGraph 发展的同时&#xff0c;也和它一起成长。 BOSS直聘和 NebulaGraph 关于 NebulaGraph 在 BOSS直聘的应…

9.13 | day 6 |day 45| to 完全平方数

● 70. 爬楼梯 &#xff08;进阶&#xff09; class Solution {public int climbStairs(int n) {int[] dp new int[n1];//设置背包容量&#xff1a;n个int m 2;//有两个物品&#xff0c;注意这是一个完全背包问题dp[0] 1;//initialize ​for(int i 1;i<n;i){//遍历背包f…

快速幂 c++

一般大家写都是 int ans 1; for (int i 1; i < a; i )ans * x;时间复杂度 但是这对于我们还不够&#xff0c;我们要 首先我们得知道一个数学知识 那么求 就有以下递归式 a 能被2整除 a 不能被2整除 (这里a/2是整除) 所以每次都调用 不就是么 最后补充一个东西…