使用按位运算符在一个int中打包多个值

时间:2011-07-02 12:24:11

标签: java bitwise-operators packing bit-packing

低级别位操作从来都不是我的强项。我将理解一些有助于理解按位运算符的以下用例.Consider ...

int age, gender, height, packed_info;

. . .   // Assign values 

// Pack as AAAAAAA G HHHHHHH using shifts and "or"
packed_info = (age << 8) | (gender << 7) | height;

// Unpack with shifts and masking using "and"
height = packed_info & 0x7F;   // This constant is binary ...01111111
gender = (packed_info >> 7) & 1;
age    = (packed_info >> 8);

我不确定这段代码是完成的以及如何完成?为什么使用幻数0x7F?如何完成包装和拆包?

Source

7 个答案:

答案 0 :(得分:58)

正如评论所说,我们打算将年龄,性别和身高打包成15位格式:

AAAAAAAGHHHHHHH

让我们从这一部分开始:

(age << 8)

首先,年龄有这种格式:

age           = 00000000AAAAAAA

其中每个A可以是0或1。

<< 8将位移到左侧8位,并用零填充间隙。所以你得到:

(age << 8)    = AAAAAAA00000000

类似地:

gender        = 00000000000000G
(gender << 7) = 0000000G0000000
height        = 00000000HHHHHHH

现在我们想将这些组合成一个变量。 |运算符通过查看每个位来工作,如果任一输入中的位为1,则返回1。所以:

0011 | 0101 = 0111

如果一个输入中的位为0,则从另一个输入获得该位。查看(age << 8)(gender << 7)height,您会看到,如果其中一个位为1,则其他位为0。所以:

packed_info = (age << 8) | (gender << 7) | height = AAAAAAAGHHHHHHH

现在我们想解开这些比特。让我们从高度开始吧。我们想得到最后7位,并忽略前8位。为此,我们使用&运算符,只有当两个输入位都是1时才返回1.所以:

0011 & 0101 = 0001

所以:

packed_info          = AAAAAAAGHHHHHHH
0x7F                 = 000000001111111
(packed_info & 0x7F) = 00000000HHHHHHH = height

为了获得年龄,我们可以将所有8个位置向右推,我们只剩下0000000AAAAAAAA。所以age = (packed_info >> 8)

最后,为了获得性别,我们将所有7个位置向右推,以摆脱高度。然后我们只关心最后一点:

packed_info            = AAAAAAAGHHHHHHH
(packed_info >> 7)     = 0000000AAAAAAAG
1                      = 000000000000001
(packed_info >> 7) & 1 = 00000000000000G

答案 1 :(得分:10)

这可能是比特操纵的一个相当长的教训,但首先让我指出bit masking article on Wikipedia

packed_info = (age << 8) | (gender << 7) | height;

取年龄并将其值超过8位然后取出性别并将其移动超过7位,高度将占据最后一位。

age    = 0b101
gender = 0b1
height = 0b1100
packed_info = 0b10100000000
            | 0b00010000000
            | 0b00000001100
/* which is */
packed_info = 0b10110001100

解包反过来但使用像0x7F(0b 01111111)这样的掩码来修剪字段中的其他值。

gender = (packed_info >> 7) & 1;

会像...一样......

gender = 0b1011 /* shifted 7 here but still has age on the other side */
       & 0b0001
/* which is */
gender = 0b1

请注意,将任何内容与1进行AND运算与“保持”该位相同,而使用0进行AND运算与“忽略”该位相同。

答案 2 :(得分:5)

如果您要将日期存储为数字,也许您可​​以通过将年份乘以10000,将月份乘以100并添加日期来完成此操作。 2011年7月2日等日期将编码为20110702:

    year * 10000 + month * 100 + day -> yyyymmdd
    2011 * 10000 + 7 * 100 + 2 -> 20110702

我们可以说我们用 yyyymmdd 掩码编码日期。我们可以将此操作描述为

  • 将第4年的职位转移到左侧,
  • 将月份2位置向左移动
  • 按原样离开。
  • 然后将这三个值组合在一起。

这与年龄,性别和身高编码相同,只是作者以二进制思考。

查看这些值可能具有的范围:

    age: 0 to 127 years
    gender: M or F
    height: 0 to 127 inches

如果我们将这些值翻译为二进制,我们就会这样:

    age: 0 to 1111111b (7 binary digits, or bits)
    gender: 0 or 1 (1 bit)
    height: 0 to 1111111b (7 bits also)

考虑到这一点,我们可以使用掩码 aaaaaaahhhhhhhh 对年龄 - 性别 - 身高数据进行编码,只是在这里我们讨论的是二进制数字,而不是十进制数字。

所以,

  • 将年龄8 向左移动
  • 将性别7 向左移动
  • 保持高度不变。
  • 然后将所有三个值组合在一起。

在二进制中,Shift-Left运算符(&lt;&lt;)将值 n 位置向左移动。 “Or”运算符(许多语言中的“|”)将值组合在一起。因此:

    (age << 8) | (gender << 7) | height

现在,如何“解码”这些值?

二进制比十进制更容易:

  • 你“掩盖”了身高,
  • 将性别7位向右移动并将其掩盖,最后
  • 将年龄8位向右移动。

Shift-Right运算符(&gt;&gt;)向右移动n个位置(无论从最右边位置“移出”的数字都丢失)。 “和”二进制运算符(在许多语言中为“&amp;”)掩盖位。为此,它需要一个掩码,指示要保留哪些位以及要销毁哪些位(保留1位)。因此:

    height = value & 1111111b (preserve the 7 rightmost bits)
    gender = (value >> 1) & 1 (preserve just one bit)
    age = (value >> 8)

由于十六进制中的1111111b在大多数语言中都是0x7f,这就是这个神奇数字的原因。使用127(十进制为1111111b)可以产生相同的效果。

答案 3 :(得分:3)

更简洁的回答:

AAAAAAA G HHHHHHH

包装:

packed = age << 8 | gender << 7 | height

或者,如果在MySQL SUM聚合函数中使用

,则可以对组件求和
packed = age << 8 + gender << 7 + height

启封:

age = packed >> 8 // no mask required
gender = packed >> 7 & ((1 << 1) - 1) // applying mask (for gender it is just 1)
height = packed & ((1 << 7) - 1) // applying mask


另一个(更长)的例子:

假设您有一个要打包的IP地址,但它是一个虚构的IP地址,例如 132.513.151.319。请注意,某些组件大于256,与实际IP地址不同,需要8位以上。

首先,我们需要弄清楚我们需要使用哪种偏移来存储最大数量。 让我们说我们虚构的IP没有任何组件可以大于999,这意味着每个组件需要10位存储(允许数字高达1014)。

packed = (comp1 << 0 * 10) | (comp1 << 1 * 10) | (comp1 << 2 * 10) | (comp1 << 3 * 10)

提供dec 342682502276bin 100111111001001011110000000010010000100

现在让我们解压缩值

comp1 = (packed >> 0 * 10) & ((1 << 10) - 1) // 132
comp2 = (packed >> 1 * 10) & ((1 << 10) - 1) // 513
comp3 = (packed >> 2 * 10) & ((1 << 10) - 1) // 151
comp4 = (packed >> 3 * 10) & ((1 << 10) - 1) // 319

其中(1 << 10) - 1是二进制掩码,我们用来隐藏左边的位,超出我们感兴趣的10个最右边的位。

使用MySQL查询的相同示例

SELECT

(@offset := 10) AS `No of bits required for each component`,
(@packed := (132 << 0 * @offset) | 
            (513 << 1 * @offset) | 
            (151 << 2 * @offset) | 
            (319 << 3 * @offset)) AS `Packed value (132.513.151.319)`,

BIN(@packed) AS `Packed value (bin)`,

(@packed >> 0 * @offset) & ((1 << @offset) - 1) `Component 1`,
(@packed >> 1 * @offset) & ((1 << @offset) - 1) `Component 2`,
(@packed >> 2 * @offset) & ((1 << @offset) - 1) `Component 3`,
(@packed >> 3 * @offset) & ((1 << @offset) - 1) `Component 4`;

答案 4 :(得分:2)

左移运算符意味着“乘以这两次”。在二进制中,将数字乘以2与向右侧添加零相同。

右移运算符与左移运算符相反。

管道运算符是“或”,意味着将两个二进制数叠加在一起,并且在任一数字中都有1,该列中的结果为1。

所以,让我们解压出packed_info的操作:

// Create age, shifted left 8 times:
//     AAAAAAA00000000
age_shifted = age << 8;

// Create gender, shifted left 7 times:
//     0000000G0000000
gender_shifted = gender << 7;

// "Or" them all together:
//     AAAAAAA00000000
//     0000000G0000000
//     00000000HHHHHHH
//     ---------------
//     AAAAAAAGHHHHHHH
packed_info = age_shifted | gender_shifted | height;

拆包是相反的。

// Grab the lowest 7 bits:
//     AAAAAAAGHHHHHHH &
//     000000001111111 =
//     00000000HHHHHHH
height = packed_info & 0x7F;

// right shift the 'height' bits into the bit bucket, and grab the lowest 1 bit:
//     AAAAAAAGHHHHHHH 
//   >> 7 
//     0000000AAAAAAAG &
//     000000000000001 =
//     00000000000000G
gender = (packed_info >> 7) & 1;

// right shift the 'height' and 'gender' bits into the bit bucket, and grab the result:
//     AAAAAAAGHHHHHHH 
//   >> 8
//     00000000AAAAAAA
age    = (packed_info >> 8);

答案 5 :(得分:1)

您可以将表达式x & mask视为从x中删除mask中不存在的位(即,值为0)的操作。这意味着,packed_info & 0x7Fpacked_info中删除了高于第七位的所有位。

示例:如果packed_info为二进制的1110010100101010,则packed_info & 0x7f将为

1110010100101010
0000000001111111
----------------
0000000000101010

因此,在height中,我们得到packed_info的低7位。

接下来,我们将整个packed_info移动7,这样我们就会删除已经读出的信息。所以我们得到(对于上一个示例中的值)111001010性别存储在下一位,因此使用相同的技巧:& 1我们只从信息中提取该位。其余信息包含在偏移量8处。

回头也不复杂:你取age,将它移8位(所以你从1110010100000000得到11100101),将gender换成7(所以你得到00000000),然后取高度(假设它适合低7位)。然后,你将所有这些组合在一起:

1110010100000000
0000000000000000
0000000000101010
----------------
1110010100101010

答案 6 :(得分:1)

我曾多次遇到同样的要求。借助Bitwise AND运算符非常容易。只需增加两(2)的幂即可使你的价值合格。要存储多个值,请添加它们的相对数(2的幂)并获得SUM。此SUM将合并您选择的值。怎么样 ?

对每个值执行按位AND,对于未选择的值,将给出零(0),为其选择非零。

以下是解释:

1)值(YES,NO,MAYBE)

2)赋予权力2(2)

YES   =    2^0    =    1    =    00000001
NO    =    2^1    =    2    = 00000010
MAYBE =    2^2    =    4    = 00000100

3)我选择YES和MAYBE因此SUM:

SUM    =    1    +    4    =    5

SUM    =    00000001    +    00000100    =    00000101 

此值将同时存储YES和MAYBE。怎么样?

1    &    5    =    1    ( non zero )

2    &    5    =    0    ( zero )

4    &    5    =    4    ( non zero )

因此,SUM由

组成
1    =    2^0    =    YES
4    =    2^2    =    MAYBE.

有关更详细的说明和实施,请访问我的blog