Bitmasking in Java with Bitwise Operators – 在Java中用位操作符进行位屏蔽

最后修改: 2021年 8月 6日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll look at how to implement low-level bitmasking using bitwise operators. We’ll see how we can treat a single int variable as a container for a separate piece of data.

在本教程中,我们将研究如何使用位操作符实现低级别的位掩码。我们将看到我们如何将一个单一的int变量作为一个独立数据的容器。

2. Bitmasking

2.比特掩码

Bitmasking allows us to store multiple values inside one numerical variable. Instead of thinking about this variable as a whole number, we treat its every bit as a separate value.

Bitmasking允许我们在一个数字变量中存储多个值。我们不把这个变量当作一个整数,而是把它的每一个比特当作一个独立的值

Because a bit can equal either zero or one, we can also think of it as either false or true. We can also slice a group of bits and treat them as a smaller number variable or even a String.

因为一个比特可以等于零或一,我们也可以把它看作是假的或真的。我们也可以把一组比特切开,把它们当作一个较小的数字变量甚至是一个String

2.1. Example

2.1.例子

Suppose we have a minimal memory footprint and need to store all information about a user’s account inside one int variable. The first eight bits (from 32 available) will store boolean information like “is the account active?” or “is the account premium?”

假设我们有一个最小的内存占用,并且需要在一个int变量内存储关于用户账户的所有信息。前八位(来自32个可用的)将存储boolean信息,如 “该账户是否激活?”或 “该账户是否溢价?”

As for the remaining 24 bits, we’ll convert them to three characters that will serve as the user’s identifier.

至于剩下的24位,我们将把它们转换成三个字符,作为用户的标识符。

2.2. Encoding

2.2.编码

Our user will have an identifier “AAA”, and he’ll have an active and premium account (stored in the first two bits). In binary representation, it will look like:

我们的用户将有一个标识符 “AAA”,他将有一个活跃的高级账户(存储在前两个位)。以二进制表示,它将看起来像。

String stringRepresentation = "01000001010000010100000100000011";

This can be easily encoded into an int variable using the built-in Integer#parseUnsignedInt method:

使用内置的Integer#parseUnsignedInt方法,可以轻松地将其编码为int变量。

int intRepresentation = Integer.parseUnsignedInt(stringRepresentation, 2);
assertEquals(intRepresentation, 1094795523);

2.3. Decoding

2.3.解码

This process can also be reversed using the Integer#toBinaryString method:

这个过程也可以使用Integer#toBinaryString方法进行逆转。

String binaryString = Integer.toBinaryString(intRepresentation);
String stringRepresentation = padWithZeros(binaryString);
assertEquals(stringRepresentation, "01000001010000010100000100000011");

3. Extracting One Bit

3.提取一个比特

3.1. First Bit

3.1.第一个比特

If we want to check the first bit of our account variable, all we need is the bitwise “and” operator and the number “one as a bitmask. Because number “one” in binary form has only the first bit set to one and the rest of them are zeros, it will erase all the bits from our variable, leaving only the first one intact:

如果我们想检查我们的账户变量的第一位,我们所需要的就是位操作符”and”和数字”one“作为一个位掩码。因为数字”one“在二进制形式中只有第一位被设置为1,其余的都是0,它将擦除我们变量中的所有位,只留下第一位

10000010100000101000001000000011
00000000000000000000000000000001
-------------------------------- &
00000000000000000000000000000001

Then we need to check if the produced value is not equal to zero:

然后我们需要检查产生的值是否不等于零。

intRepresentation & 1 != 0

3.2. Bit at Arbitrary Position

3.2.位在任意位置

If we want to check some other bit, we need to create an appropriate mask, which needs to have a bit at the given position set to one and the rest set to zeros. The easiest way to do that is to shift the mask we already have:

如果我们想检查其他的位,我们需要创建一个适当的掩码,这个掩码需要在给定的位置有一个位被设置为1,其余的被设置为0。最简单的方法是转移我们已经有的掩码。

1 << (position - 1)

The above line of code with the position variable set to 3 will change our mask from:

上面这行代码中的position变量设置为3,将把我们的面具从。

00000000000000000000000000000001
to:

00000000000000000000000000000001
到。

00000000000000000000000000000100

So now, the bitwise equation will look like this:

因此,现在,比特方程将看起来像这样。

10000010100000101000001000000011
00000000000000000000000000000100
-------------------------------- &
00000000000000000000000000000000

Putting all of this together, we can write a method for extracting a single bit at the given position:

把所有这些放在一起,我们可以写一个方法来提取给定位置上的单个比特。

private boolean extractValueAtPosition(int intRepresentation, int position) {
    return ((intRepresentation) & (1 << (position - 1))) != 0;
}

To the same effect, we could also shift the intRepresentation variable in the reverse direction instead of changing the mask.

为了达到同样的效果,我们也可以将intRepresentation变量向相反方向移动,而不是改变掩码。

4. Extracting Multiple Bits

4.提取多个比特

We can use similar methods to extract multiple bits from an integer. Let’s extract the last three bytes of our user account variable and convert them into a string. First, we need to get rid of the first eight bits by shifting the variable to the right:

我们可以用类似的方法从一个整数中提取多个比特。让我们提取我们的用户账户变量的最后三个字节,并将其转换为字符串。首先,我们需要通过将变量向右移动来摆脱前八位的影响

int lastThreeBites = intRepresentation >> 8;
String stringRepresentation = getStringRepresentation(lastThreeBites);
assertEquals(stringRepresentation, "00000000010000010100000101000001");

We still have 32 bits because the int will always have 32 bits. However, now we’re interested in the first 24 bits, and the rest of them are zeros and will be easy to ignore. The int variable we created could be easily used as an integer ID, but because we want to have a string ID, we have one more step to do.

我们仍然有32位,因为int将永远有32位。然而,现在我们感兴趣的是前24位,其余的是零,将很容易被忽略。我们创建的int变量可以很容易地用作一个整数ID,但是因为我们想有一个字符串ID,所以我们还有一个步骤要做。

We’ll split the string representation of the binary into groups of eight characters, parse them to char variables, and join them into one final String.

我们将把二进制的字符串表示法分成8个字符一组,把它们解析为char变量,然后把它们连接成一个最终的String

For convenience, we’ll also ignore empty bytes:

为方便起见,我们还将忽略空字节。

Arrays.stream(stringRepresentation.split("(?<=\\G.{8})"))
  .filter(eightBits -> !eightBits.equals("00000000"))
  .map(eightBits -> (char)Integer.parseInt(eightBits, 2))
  .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
  .toString();

5. Applying a Bitmask

5.应用一个比特掩码

Instead of extracting and checking values of single bits, we can also create a mask to check many of them at the same time. We want to check if our user has an active and premium account, so his variable has the first two bits both set to one.

我们也可以创建一个掩码来同时检查许多位的值,而不是提取和检查单个位的值。我们想检查我们的用户是否有一个活跃的高级账户,所以他的变量的前两个比特都设置为1。

We could check them separately using previous methods, but it’s faster to create a mask that will select them both:

我们可以用以前的方法分别检查它们,但创建一个能同时选择它们的蒙版会更快。

int user = Integer.parseUnsignedInt("00000000010000010100000101000001", 2);
int mask = Integer.parseUnsignedInt("00000000000000000000000000000011", 2);
int masked = user & mask;

Because our user has an active account, but it’s not premium, the masked value will have only the first bit set to one:

因为我们的用户有一个活跃的账户,但它不是高级账户,所以被屏蔽的值将只有第一个比特被设置为1。

assertEquals(getStringRepresentation(masked), "00000000000000000000000000000001");

Now, we can easily and cheaply assert whether a user meets our conditions:

现在,我们可以轻松而廉价地断言一个用户是否符合我们的条件。

assertFalse((user & mask) == mask);

6. Conclusion

6.结语

In this tutorial, we learned how to use bitwise operators to create bitmasks and apply them to extract binary information from integers. As always, all the code examples are available over on GitHub.

在本教程中,我们学习了如何使用位运算符来创建位掩码,并应用它们来从整数中提取二进制信息。一如既往,所有的代码实例都可以在GitHub上找到