Перейти к основному содержимому

Битовая маска в Java с побитовыми операторами

· 5 мин. чтения

1. Обзор

В этом руководстве мы рассмотрим, как реализовать низкоуровневую битовую маску с помощью побитовых операторов. Мы увидим, как мы можем рассматривать одну переменную типа int как контейнер для отдельного фрагмента данных.

2. Битовая маска

Битовая маска позволяет нам хранить несколько значений внутри одной числовой переменной. Вместо того, чтобы думать об этой переменной как о целом числе, мы рассматриваем каждый ее бит как отдельное значение .

Поскольку бит может равняться либо нулю, либо единице, мы также можем считать его либо ложным, либо истинным. Мы также можем разделить группу битов и рассматривать их как переменную с меньшим числом или даже как String .

2.1. Пример

Предположим, у нас минимальный объем памяти, и нам нужно хранить всю информацию об учетной записи пользователя внутри одной переменной типа int . Первые восемь битов (из 32 доступных) будут хранить логическую информацию, такую как «активна ли учетная запись?» или «это премия за аккаунт?»

Что касается оставшихся 24 бит, мы преобразуем их в три символа, которые будут служить идентификатором пользователя.

2.2. Кодирование

У нашего пользователя будет идентификатор «ААА», и у него будет активный и премиум-аккаунт (хранится в первых двух битах). В двоичном представлении это будет выглядеть так:

String stringRepresentation = "01000001010000010100000100000011";

Это можно легко закодировать в переменную типа int с помощью встроенного метода Integer#parseUnsignedInt :

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. Первый бит

Если мы хотим проверить первый бит нашей переменной учетной записи, все, что нам нужно, это побитовый оператор « и» и число « один » в качестве битовой маски. Поскольку у числа « один » в двоичной форме только первый бит установлен в единицу, а остальные — нули, оно удалит все биты из нашей переменной, оставив только первый нетронутым :

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

Затем нам нужно проверить, не равно ли полученное значение нулю:

intRepresentation & 1 != 0

3.2. Бит в произвольной позиции

Если мы хотим проверить какой-то другой бит, нам нужно создать соответствующую маску, в которой бит в данной позиции должен быть установлен в единицу, а остальные — в нули . Самый простой способ сделать это — сдвинуть маску, которая у нас уже есть:

1 << (position - 1)

Приведенная выше строка кода с переменной position , установленной на 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 можно было бы легко использовать в качестве целочисленного идентификатора , но поскольку мы хотим иметь строковый идентификатор, нам нужно сделать еще один шаг.

Мы разделим строковое представление двоичного файла на группы по восемь символов, разберем их на переменные 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. Применение битовой маски

Вместо того, чтобы извлекать и проверять значения отдельных битов, мы также можем создать маску для одновременной проверки многих из них. Мы хотим проверить, есть ли у нашего пользователя активная и премиальная учетная запись, поэтому его переменная имеет первые два бита, равные единице.

Мы могли бы проверить их по отдельности, используя предыдущие методы, но быстрее создать маску, которая выберет их обоих:

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

Поскольку у нашего пользователя есть активная учетная запись, но она не премиум-класса, в замаскированном значении будет установлен только первый бит, равный единице:

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

Теперь мы можем легко и дешево проверить, соответствует ли пользователь нашим условиям:

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

6. Заключение

В этом руководстве мы узнали, как использовать побитовые операторы для создания битовых масок и применять их для извлечения двоичной информации из целых чисел. Как всегда, все примеры кода доступны на GitHub .