/**
*@global {Uint8Array} arr - 即字节流如[0xff,0xaa,0x10...]
*@global {Number} pos - 二进制流索引,比如0的时候为这个buffer的二进制第0位
*
*@param {Number} size - 位数,范围可为1~31
*@return {Number}
*/
function read(size) {
var i, code = 0;
for (i = 0; i < size; i++) {
if (arr[pos >> 3] & 1 << (pos & 7)) {
code |= 1 << i;
}
pos++;
}
return code;
}
说明
这个函数是用来读取那些二进制流中,读取规则并不是8bit 16bit 64bit的数据格式。
典型场景为读取gif数据流做解码时,gif支持自定义位数编码,比如对于黑白二值化的gif图,可以通过用2个bit编码像素,相比8个bit编码像素可以大大减少文件体积。
解析
1. 索引pos右移3位 pos>>3
可以先看看pos>>3的几个结果是什么:
0>>3 //0
7>>3 //0
8>>3 //1
15>>3 //1
16>>3 //2
255>>3 //31
256>>3 //32
分析计算过程,对于任意二进制,可以表示为十进制计算为:
当往右移动三位,即丢弃末尾三位,然后整体除以
即
又
所以右移之后的结果相当于把一个数除以8之后向下取整,即Math.floor(n/8);
而一个字节刚好是8位二进制。
所以,pos>>3代表的是,在二进制流中,当前位数对应到字节码里面是第几个字节。
2. 索引pos与7 pos & 7
同样,先看看pos & 7的结果:
0 & 7 // 0
1 & 7 // 1
6 & 7 // 6
7 & 7 // 7
8 & 7 // 0
...
63 & 7 //7
64 & 7 //0
65 & 7 //1
可以发现,计算的结果相当于对一个数求8的余数,相当于n%8。
分析计算过程,可以把等式化为二进制:
// 0 & 7 = 0
0b00000000 & 0b00000111 //-> 0b00000000
// 1 & 7 = 1
0b00000001 & 0b00000111 //-> 0b00000001
// 63 & 7 = 7
0b00111111 & 0b00000111 //-> 0b00000111
// 64 & 7 = 0
0b01000000 & 0b00000111 //-> 0b00000000
// 65 & 7 = 1
0b01000001 & 0b00000111 //-> 0b00000001
任意数字与7(n & 0b111),都是提取该数二进制模式的末三位;
因此无论所给的数字多大,末三位一定是按顺序在0b000到0b111之间,也就是0到7;
因此结果跟求8的余数结果相同。
所以,pos & 7表示的是,当前位数对应到一个字节内第几位
3. 判断值arr[pos >> 3] & 1 << (pos & 7)
由上可知:
arr[pos >> 3]取到的是当前的字节,
pos & 7则是代表当前是字节内的第几位。
因为1的二进制值为0b00000001,
根据位运算"与"的特性,任意数字与1(n & 0b00000001),可以提取该数的末位的值;
发生左移时,
左移1位1<<1为0b00000010, 则可以提取倒数1位的值
左移2位1<<2为0b00000100, 则可以提取倒数2位的值
以此类推,
arr[pos >> 3] & 1 << (pos & 7)则可以获得索引pos对应的二进制值0或1
4. code |= 1 << i
当索引pos取到的值为1的时候,把值拼到code里面。
for循环之后,就可以拿到具体值。
限制
因为js位运算中左右移运算最大只支持31位,因此参数size的最大只能为31。而且跨字节读取数据还需要考虑大端序小端序的问题。因此最好是使用在小于8bit的场景下。
优点
类型确定,可以避免数字转二进制字符串带来的性能开销。