1. 概述

在本教程中,我们将了解如何使用按位运算符实现低级位掩码。我们将看到如何将单个 int 变量视为单独数据块的容器,类似于BitSet

2. 位掩码

位掩码允许我们在一个数值变量中存储多个值。 我们不将该变量视为一个整数,而是将其每一位视为一个单独的值

因为一位可以等于 0 或 1,所以我们也可以将其视为 false 或 true。我们还可以对一组位进行切片并将它们视为较小的数字变量甚至 String

2.1.例子

假设我们的内存占用量很小,并且需要将有关用户帐户的所有信息存储在一个 int 变量中。前八位(可用的 32 位)将存储 布尔 信息,例如“帐户是否处于活动状态?”或“该账户是否溢价?”

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

2.2.编码

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

String stringRepresentation = "01000001010000010100000100000011";

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

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

2.3.解码

也可以使用 Integer#toBinaryString 方法反转此过程:

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

3. 提取一位

3.1.第一个比特

如果我们想检查帐户变量的第一位,我们需要的只是按位“ and” 运算符和数字“ one 作为位掩码。因为二进制形式的数字“ ”只有第一位设置为 1,其余部分为零, 因此它将擦除变量中的所有位,仅保留第一位完好无损

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

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

intRepresentation & 1 != 0

3.2.位在任意位置

如果我们想检查其他位,我们需要 创建一个适当的掩码,它需要将给定位置的一位设置为 1,其余设置为 0 。最简单的方法是改变我们已有的掩码:

1 << (position - 1)

上面将 位置 变量设置为 3 的代码行会将我们的掩码更改为:

00000000000000000000000000000001
到:

00000000000000000000000000000100

所以现在,按位方程将如下所示:

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

将所有这些放在一起,我们可以编写一个在给定位置提取单个位的方法:

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

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

4. 提取多个位

我们可以使用类似的方法从整数中提取多个位。让我们提取用户帐户变量的最后三个字节并将它们转换为字符串。首先, 我们需要通过将变量右移来去掉前八位

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

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

我们将二进制的字符串表示形式分成八个字符的组,将它们解析为 char 变量,并将它们连接成一个最终的 String

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

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. 应用位掩码

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

我们可以使用以前的方法单独检查它们,但创建一个同时选择它们的掩码会更快:

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

因为我们的用户有一个活跃帐户,但不是高级帐户,所以屏蔽值将只有第一位设置为 1:

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

现在,我们可以轻松且廉价地断言用户是否满足我们的条件:

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

六,结论

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