我正在开发一个包含 WebSocket 的项目,服务器 (Node.js) 和客户端 (Chrome) 之间的数据是使用我设置的用于数据交换的自定义(非常简单)格式发送的。
我以 3 位为单位发送数据,因为我发送的项目都有 8 种可能性。数据格式如下所示:
0 1
bit index 01234567 8901...
item aaabbbcc cddd...
目前,我正在从字节中解析项目,如下所示:
var itemA = bytes[0] >> 5;
var itemB = (bytes[0] >> 2) & 7;
var itemC = (bytes[0] & 3) << 1 | bytes[1] >> 7;
var itemD = (bytes[1] >> 4) & 7;
就个人而言,这感觉太复杂了。问题是它很复杂,因为我以字节为单位获取数据,这是 8 的倍数。为了解析 3 位的项目,我必须进行位移位,执行 AND 运算,并且因为 8 不能被 3 整除有时甚至必须组合两个字节的一部分,例如
itemC
。
以 3 位组而不是 8 位组的形式读取该数据会更有效。
我想出的是使用
.toString(2)
将所有字节转换为位到字符串,然后使用 .substring
获取长度为 3 的子字符串,并使用 parseInt(bitString, 2)
转换回数字,但我想那就是不是这样做的方法,因为字符串操作很慢,而且我实际上没有做任何与字符串相关的事情。
是否可以读取一组位,例如3而不是从字节解析它们?或者有没有更有效的方法从字节中读取位?
二进制 AND 和移位运算是最快的方法。它们可以很好地翻译成机器代码指令。进一步加快速度的唯一方法是牺牲带宽来提高速度,例如,每个字节不使用超过 3 位,但从您的问题来看,您可能已经考虑并拒绝了这种权衡。
function byte2bits(a)
{
var tmp = "";
for(var i = 128; i >= 1; i /= 2)
tmp += a&i?'1':'0';
return tmp;
}
function split2Bits(a, n)
{
var buff = "";
var b = [];
for(var i = 0; i < a.length; i++)
{
buff += byte2bits(a[i]);
while(buff.length >= n)
{
b.push(buff.substr(0, n));
buff = buff.substr(n);
}
}
return [b, buff];
}
var a, b, r;
a = [227, 142];
[b, r] = split2Bits(a, 3);
//b = ["111", "000", "111", "000", "111"];
//r = '0'; //rest of bits
如果注意字节顺序,您可以将其作为 int 或 long int 数组访问。 还有一种不使用位 3 和位 7 的可能性
我们可以通过获取适当的 16 位整数然后对其进行位移来获得我们需要的值。
很明显,要获得
i-th
值,我们应该获得 16 位整数,其偏移量(以字节为单位)适合 (bits * (i + 1) - 16)/8 <= offset <= (bits * i)/8
。
让我们取
M=bits*i/8
,所以我们有M + bits/8 - 2<= offset <= M
。然后我们得到最小偏移量ceil(M + bits/8 - 2)
并通过偏移量计算第i个值在16位整数中的位置。我刚刚写了以下函数
function getDataFromStream(buffer, bitsPerValue, endianness) {
var valuesCount = Math.floor(buffer.length * 8 / bitsPerValue);
var ret = new Buffer(valuesCount);
if (valuesCount > 0) {
for (var i = 0; i < valuesCount; i++) {
var offsetMin = Math.ceil(bitsPerValue * i / 8. + bitsPerValue / 8. - 2);
if (offsetMin < 0) {
offsetMin = 0;
}
if(endianness == 'BE')
var wordWithValue = buffer.readUInt16BE(offsetMin, true);
else
var wordWithValue = buffer.readUInt16LE(offsetMin, true);
var offsetInWord = bitsPerValue * i - offsetMin * 8;
var leftInWord = 16 - bitsPerValue - offsetInWord;
// then get value in the word by shifting and then remove other bits by "%"
ret[i] = (wordWithValue >> (endianness == 'BE' ? leftInWord : offsetInWord )) % Math.pow(2, bitsPerValue);
}
}
return ret;
}
以下示例从 Buffer 中读取 5 个字节长度的 8 个 5 位值。
// buffer with 5 bytes
var xx = new Buffer(5);
xx[0] = 255;
xx[1] = 255;
xx[2] = 255;
xx[3] = 255;
xx[4] = 250;
// get data, 5bits per value.
var yy = getDataFromStream(xx, 5, 'BE');
console.log('got buffer with length='+ yy.length);
for(i = 0; i < yy.length; i++){
console.log(i+'-'+yy[i]);
}
当我启动 Node test.js 时,我得到了
got buffer with length=8
0-31
1-31
2-31
3-31
4-31
5-31
6-31
7-26
如果你有多个固定长度(即你总是可以保证它是2个字节),你可以这样读取这些位:
// convert to binary
const num = 256;
const numAsBinaryString = num.toString(2);
const leastSignificantByteAsBinaryString = numAsBinaryString.substr(8);
const [
eighthBit,
seventhBit,
sixthBit,
fifthBit,
fourthBit,
thirdBit,
secondBit,
firstBit,
] = leastSignificantByteAsBinaryString.join('');
我知道的最短方法是这样的:
function isBitSet(value, bit) {
return !!(bit === 1 ? value & 1 : value & Math.pow(2,bit));
}
这是假设
value
是一个“大端”字节(数字的左侧先于右侧写入内存)。如果不是,您需要先将其转换。
此函数也适用于任何位大小的数字(16、32、64...)。
如何运作: 因为从最右边开始,每个位代表一个等效的“十进制”值,等于 2(代表二进制系统中的 2 个符号)的位位置次方(从 1 开始,不包括第一个为 1 的位)-如果设置了该位,则将其与原始值进行“与”操作将始终返回一个非零值 - Math.pow 的结果(也是真值),如果不是,则返回 0(假值)。