Java: использование побитовых операций

Author: Tatyana Milkina

1. Побитовые (поразрядные) операции

В следующей таблице представлены побитовые операции применяемые в языке Java:

Операция
Описание
~
Поразрядная унарная операция НЕ (NOT, дополнение)
&
Поразрядная логическая операция И (AND, поразрядная конъюнкция)
|
Поразрядная логическая операция ИЛИ (OR, побитовая дизъюнкция)
^
Поразрядная логическая операция исключающее ИЛИ (XOR)

Побитовые операторы применяются к целочисленным типам long, int, short, byte, char.  Побитовые операторы применяются к каждому отдельному биту каждого операнда.

Результаты выполнения побитовых логических операций

A

B

A | B

A & B

A ^ B

~A

0

0

0

0

0

1

1

0

1

0

1

0

0

1

1

0

1

1

1

1

1

1

0

0

1.1. Побитовое ИЛИ (OR, |)

Результирующий бит, полученный в результате выполнения оператора OR, равен 1, если соответствующий бит в любом из операндов равен 1.
'  00101010   42
 | 00001111   15 
   -------------- 
   00101111   47
'

1.2. Побитовое И (AND, &)

Значение бита, полученное в результате выполнения побитового оператора AND, &, равно 1, если соответствующие биты в операндах также равны 1. Во всех остальных случаях значение результирующего бита равно 0.
' 00101010   42 
& 00001111   15
  --------------
  00001010   10
'

1.3. Побитовое исключающее ИЛИ (XOR, ^)

Результирующий бит, полученный в результате выполнения оператора XOR, ^, равен 1, если соответствующий бит только в одном из операндов равен 1. Во всех других случаях результирующий бит равен 0.
' 00101010  42 
^ 00001111  15
 --------------
  00100101  37 
'

1.4. Побитовое НЕ (NOT, ~)

Унарный оператор NOT (Не), ~, называемый также побитовым дополнением, инвертирует все биты операнда.
~ 00101010   42
-----------------
  11010101

Рассмотрим теперь применение побитовых операций в программе. В следующем примере также показано применение метода Integer.toBinaryString(), который приводит десятичное значение к двоичному:

public class BitwiseExample1 {
    public static void main(String[] args) {
        int a = 3;
        int b = 6;
        int c = a | b;
        int d = a & b;
        int e = a ^ b;
        int f = ~b;
        System.out.println("a = " + Integer.toBinaryString(a));
        System.out.println("b = " + Integer.toBinaryString(b));
        System.out.println("a | b = " + Integer.toBinaryString(c));
        System.out.println("a & b = " + Integer.toBinaryString(d));
        System.out.println("a ^ b = " + Integer.toBinaryString(e));
        System.out.println("~ b = " + Integer.toBinaryString(f));
    }
}

2. Битовые сдвиги в Java

Операция
Описание
>>
Сдвиг вправо (арифметический сдвиг)
>>>
Сдвиг вправо с заполнением нулями (беззнаковый сдвиг)
<<
Сдвиг влево
Битовые сдвиги смещают все двоичные разряды значения на указанное количество позиций.

Общая форма:

значение << количество
Например:
34<<3, 56>>2, 78>>>1
Для того чтобы понять, как происходит сдвиг, лучше рассмотреть его на примере двоичных чисел:
0001<<1 = 0010
0100>>1 = 0010

При сдвиге отрицательных чисел имеется разница в использовании операторов >> и >>>. Операция >> распространяет знаковый (левый) бит направо до конца, >>> заполняет нулями. У положительных чисел результат будет одинаков.

Типы byte и short продвигаются к типу int при вычислении выражения.

Пример битовых сдвигов:

int i = 192;
i       00000000 00000000 00000000 11000000 (192)
i<<1    00000000 00000000 00000001 10000000 (384)
i>>1    00000000 00000000 00000000 01100000 (96)
i>>>1   00000000 00000000 00000000 01100000 (96) 
int i = -192; (двоичная запись в доп. коде)
i       11111111 11111111 11111111 01000000 (-192) 
i<<1    11111111 11111111 11111110 10000000 (-384)
i>>1    11111111 11111111 11111111 10100000 (-96)
i>>>1   01111111 11111111 11111111 10100000 (2147483552)
public class BitwiseExample2 {
    public static void main(String[] args) {
        byte a = 64; //0100 0000
        byte b;
        int i = a << 2; // 1 0000 0000
        b = (byte) (a << 2); //0000 0000
        System.out.println("a = " + a);
        System.out.println("i = " + i);
        System.out.println("b = " + b);
    }
}

3. Побитовые операции с присваиванием

Также существуют побитовые операции с присваиванием:

Операция
Описание
&=
Поразрядная логическая операция И с присваиванием
|=
Поразрядная логическая операция ИЛИ с присваиванием
^=
Поразрядная логическая операция исключающее ИЛИ с присваиванием
>>­=
Сдвиг вправо с присваиванием
>>>=
Сдвиг вправо с заполнением нулями и присваиванием
<<=
Сдвиг влево с присваиванием

4. Практическое применение побитовых операций

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

4.1. Чётность числа

x & 1 - проверяет чётность числа. Если число четное - результат 0, нечетное - 1.

4.2. Деление числа на два

  • x<<1 – умножение на 2.
  • x>>1 - деление на два с отбрасыванием любого остатка.

4.3. Шифрование числа

Операция XOR при применении два раза к одному и тому же битовому массиву восстанавливает ее исходное значение. Это можно использовать при шифровании данных при передаче по сети: 

  C = A ^ B

  A = C ^ B 

Представьте, что необходимо отправить в сообщении число 560 - пин-код от банковской  карты. Если злоумышленник перехватит сообщение, то узнает пин-код и сможет воспользоваться им. Только отправитель и получатель могут знать пин-код. Чтобы этого не произошло, придумаем какое-то число - маску и сообщим его получателю заранее. Перед отправкой пин-кода, зашифруем его - применим побитовую операцию XOR: message^XOR. И результат отправим. Если злоумышленник и перехватит сообщение, он не будет знать как его расшифровать. Адресат получает сообщение, расшифровывает пин-код с помощью имеющейся маски: message^maska.

Следующий код иллюстрирует этот пример:

public class BitwiseExample3 {
    public static void main(String[] args) {
        int message = 560;
        int maska = 67;
        int codedMessage = message ^ maska;
        int receivedMesssage = codedMessage ^ maska;
        System.out.println("message = " + message);
        System.out.println("message = " + Integer.toBinaryString(message));
        System.out.println("codedMessage = " + codedMessage);
        System.out.println("codedMessage = " + Integer.toBinaryString(codedMessage));
        System.out.println("receivedMesssage = " + receivedMesssage);
        System.out.println("receivedMesssage = " + Integer.toBinaryString(receivedMesssage));
    }
}

4.4. Наложение маски

Маска позволяет получать значения только определенных битов в последовательности. Например, у нас есть маска 00100100. Она позволяет нам получать из последовательности только те биты, которые в ней установлены. В данном случае это 3-й и 7-й разряд. Для этого достаточно выполнить побитовое AND с нашей маской и выбранным числом:
'      001010101 
&      000100100 
    ---------------- 
       000000100
'

 

Презентацию с видео можно скачать на Patreon.

Читайте также:
Комментарии
TTereshchenko
May 19, 2021
3. Побитовые операции с присваиванием. >>­Сдвиг вправо с присваиванием - отсутствует "="
sysadmin
May 19, 2021
Спасибо! Исправлено.
AlexBRD
Oct 22, 2022
Побитовые операторы выполняют побитовые действия над двумя выражениями с любым типом данных, относящимся к категории типа данных integer. При чём здесь символьный тип данных "char"?
milkina
Oct 24, 2022
Тип char совместим с целочисленными типами. К нему можно применять арифметические операции, а также побитовые операции.