从这篇文章你将了解到什么?
ArrayBuffer的作用
左位移和右位移运算的使用
按位与运算的使用
之前介绍了二进制相关的知识 二进制转十进制心算大法, 本篇将使用JavaScript开发一个相关的可视化工具,实现十进制和二进制之间的自动转换。
当然,醉翁之意不在酒。在开发的过程中熟悉二进制的位运算才是本篇的关注点。
转换工具介绍
下面来看看这个可视化工具。
输入一个十进制的正整数(32位无符号整数), 我们会将该数字的二进制展示到一个表格中。
表格由32个单元格组成, 因为1字节=8字位, 所以单元格按八个一组来划分。
比如, 输入一个数字7, 对应的二进制就是111,那么表格就应该是下面这样:
每个单元格上的二进制数字都可以在0和1之间切换, 对应的也会计算出这组二进制数代表的十进制数。
比如, 我们把下图选中的单元格从0切换为1,那么对应的十进制数也会跟着变为263:
ArrayBuffer介绍
下面我们用数字263作为例子,讲讲如何使用位运算操作字节流的方式实现代码。
首先,初始化的时候,我们会把263这个数字放入一个数组,然后把这个数组转为字节流存放到ArrayBuffer内存当中 。
如图,下方的矩形ArrayBuffer表示一段内存,但是我们不能直接操作它。
这时候我们就要用到JS里的TypedArray来访问这一段内存,MDN把TypedArray称为“Multiple views on the same data”。
我们可以使用Uint32Array,Uint8Array等等这些“View”来对ArrayBuffer内存进读写。
这样一来,我们的“十进制展示区” 和“二进制展示区”都可以从同一块内存中读取数据,不用浪费另外的数组空间去存放一大堆的0和1。
另外,在改变“二进制展示区”单元格数值时,我们可以直接对ArrayBuffer内存中的数据进行写操作,省去了很多麻烦。
下面请看代码,我们使用Uint32Array来表示“十进制展示区” 。
let target = new Uint32Array([263])
将数组[263]转为字节流,再读取为一个由32位无符号字节组成的数组,于是变量target赋值的数组就是[263]。target[0]就是图中“十进制展示区”的263。
我们使用Uint8Array来表示“二进制展示区”。
let bytes = new Uint8Array(target.buffer)
通过target.buffer可以读取到内存中存放的字节流,将其读取为一个由8位无符号字节组成的数组,得到的bytes数组就是[7,1,0,0], 对应的二进制数组就是[0000111,00000001, 00000000, 00000000]。
接下来我们要将bytes数组显示到“二进制展示区”中。
因为有32个单元格, 我们从第0格遍历到第31格, 每个单元格都是通过getBit方法从bytes数组中获取对应的二进制数值。
function writeBits() {
for (var i = 0; i < 32; i++) {
单元格[i].textContent = getBit(i);
}
}
下面是getBit的具体实现代码。
function getBit(bit) {
return bytes[bit >> 3] & (0x1 << (bit & 0x7)) ? 1 : 0;
}
我们一段段来解释下。
“bit>>3”分组
“>>”是右位移运算符,如果n是整数,那么n>>3效果等同n除以8取除数,可以用于分组;
“bit&7”求余
如果n是整数,n&7效果等同n除以8取余数,可以用于确定n在所属分组中的位置;
“&”运算和“<<”运算
我们知道按位与运算的规则是下面这样:
1&1=1
0&1=0
1&0=0
0&0=0
也即是说, 如果我们想要知道二进制数字"0100 0n01"中的n是0还是1,可以这样:
0x01000n01 & 0x00000100
如果结果等于0,那么n的值就是0;如果结果大于0,那么n的值就是1。
在按位与运算的规则下,0x00000100就是一个“取值器”。我们可以通过左位移运算符得到一个“取值器”。
0x1 << n的位置
把0x0000 0001中的1移动到n的位置,也就是0x0000 0100。
例子讲解
关键的位运算技巧都讲完之后,我们用一个例子来感受下。
比如我们要得到第2个单元格(从第0格算起)在bytes数组[0000111,00000001, 00000000, 00000000]中对应的数值,具体的过程就是这样:
第一步,通过2>>3=0,可以计算出第2个单元格属于第0组,也就是bytes数组中下标为0的元素。
第二步,通过2&7=0,可以计算出第2个单元格属于第一组第2个格(从第0格算起)。
第三步,制作“取值器”,0x1<<2, 得到00000100。
最后,通过"bytes[0]&取值器"判断结果是大于0还是等于0, 就可以得到第2个单元格的值了。
let res = 0x00000111 & 0x00000100 ? 1 : 0
res的值为1, 也就是第2个单元格的值为1。是不是很好玩?
未完待续......
往期回顾
网友评论