如何高效地从字节中读取位?

问题描述 投票:0回答:6

我正在开发一个包含 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而不是从字节解析它们?或者有没有更有效的方法从字节中读取位?

javascript parsing node.js byte bit-manipulation
6个回答
7
投票

二进制 AND 和移位运算是最快的方法。它们可以很好地翻译成机器代码指令。进一步加快速度的唯一方法是牺牲带宽来提高速度,例如,每个字节不使用超过 3 位,但从您的问题来看,您可能已经考虑并拒绝了这种权衡。


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

1
投票

如果注意字节顺序,您可以将其作为 int 或 long int 数组访问。 还有一种不使用位 3 和位 7 的可能性


1
投票

我们可以通过获取适当的 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

0
投票

如果你有多个固定长度(即你总是可以保证它是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('');

0
投票

我知道的最短方法是这样的:

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(假值)。

© www.soinside.com 2019 - 2024. All rights reserved.