1. 概要

このチュートリアルでは、ビット演算子を使用して低レベルのビットマスキングを実装する方法を見ていきます。 単一のint変数を個別のデータのコンテナーとして処理する方法を説明します。

2. ビットマスキング

ビットマスキングを使用すると、1つの数値変数内に複数の値を格納できます。 この変数を整数として考えるのではなく、すべてのビットを個別の値として扱います

ビットは0または1のいずれかに等しくなる可能性があるため、falseまたはtrueのいずれかと考えることもできます。 また、ビットのグループをスライスして、それらをより小さな数の変数またはStringとして扱うこともできます。

2.1. 例

最小限のメモリフットプリントがあり、ユーザーのアカウントに関するすべての情報を1つのint変数内に格納する必要があるとします。 最初の8ビット(32個から利用可能)には、「アカウントはアクティブですか?」などのboolean情報が格納されます。 または「アカウントはプレミアムですか?」

残りの24ビットは、ユーザーの識別子となる3文字に変換します。

2.2. エンコーディング

ユーザーには「AAA」という識別子があり、アクティブでプレミアムなアカウント(最初の2ビットに保存されている)があります。 バイナリ表現では、次のようになります。

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. 1ビットの抽出

3.1. 最初のビット

アカウント変数の最初のビットを確認する場合、必要なのはビット単位の「 and」演算子と、ビットマスクとしての数値「 one だけです。 。 バイナリ形式の数値「one」では最初のビットのみが1に設定され、残りはゼロであるため、変数からすべてのビットが消去され、最初のビットのみがそのまま残ります[ X194X]:

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

次に、生成された値がゼロに等しくないかどうかを確認する必要があります。

intRepresentation & 1 != 0

3.2. 任意の位置でビット

他のビットをチェックしたい場合は、適切なマスクを作成する必要があります。このマスクでは、指定された位置のビットを1に設定し、残りを0に設定する必要があります。 これを行う最も簡単な方法は、すでに持っているマスクをシフトすることです。

1 << (position - 1)

position 変数が3に設定された上記のコード行は、マスクを次のように変更します。

00000000000000000000000000000001 に:

00000000000000000000000000000100

したがって、ビット単位の方程式は次のようになります。

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

これらすべてをまとめると、指定された位置で1ビットを抽出するためのメソッドを記述できます。

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

同じ効果で、マスクを変更する代わりに、intRepresentation変数を逆方向にシフトすることもできます。

4. 複数のビットを抽出する

同様の方法を使用して、整数から複数のビットを抽出できます。 ユーザーアカウント変数の最後の3バイトを抽出し、それらを文字列に変換してみましょう。 まず、変数を右にシフトして最初の8ビットを取り除く必要があります

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

int は常に32ビットであるため、まだ32ビットがあります。 ただし、ここで最初の24ビットに関心があり、残りはゼロであり、無視しやすいでしょう。 作成したint変数は整数IDとして簡単に使用できますが、文字列IDが必要なため、もう1つの手順を実行する必要があります。

バイナリの文字列表現を8文字のグループに分割し、それらを char 変数に解析して、1つの最終的な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. ビットマスクの適用

単一ビットの値を抽出してチェックする代わりに、それらの多くを同時にチェックするためのマスクを作成することもできます。 ユーザーがアクティブでプレミアムなアカウントを持っているかどうかを確認したいので、彼の変数の最初の2ビットは両方とも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);

6. 結論

このチュートリアルでは、ビット演算子を使用してビットマスクを作成し、それらを適用して整数からバイナリ情報を抽出する方法を学習しました。 いつものように、すべてのコード例はGitHubから入手できます。