1. Побитовый сдвиг влево

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

Чтобы сдвинуть биты числа влево, нужно использовать оператор побитового сдвига влево. Записывается это так:

a << b

Где, а — число, биты которого сдвигаем, а b — число, означающее, на сколько бит влево нужно сдвинуть биты числа a. При этом справа к числу добавляются нулевые биты.

Примеры:

Пример Результат
0b00000011 << 1
0b00000110
0b00000011 << 2
0b00001100
0b00000011 << 5
0b01100000
0b00000011 << 20
0b001100000000000000000000

Сдвиг на один разряд влево дает тот же эффект, что и умножение числа на 2.

Хотите умножить число на 16 ? 16 = это 24. Значит нужно сдвинуть число на 4 разряда влево


2. Побитовый сдвиг вправо

Так же биты можно сдвигать вправо. Для этого используется оператор побитового сдвига вправо. Записывается он так:

a >> b

Где, а — число, биты которого сдвигаем, а b — число, означающее, на сколько бит вправо нужно сдвинуть биты числа a.

Примеры:

Пример Результат
0b11000011 >> 1
0b01100001
0b11000011 >> 2
0b00110000
0b11000011 >> 5
0b00000110
0b11000011 >> 20
0b00000000

Сдвиг на один разряд вправо дает тот же эффект, что и деление числа на 2 нацело.

При этом слева к числу добавляются нулевые биты, но не всегда!

Важно!

Самый левый бит числа называется битом знака: хранит 0, если число положительное и 1, если число отрицательное.

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

Но в примере выше этого не происходит. Почему? Все дело в том, что целочисленные литералы имеют тип int и 0b11111111 на самом деле значит 0b00000000000000000000000011111111. Т.е. самый левый бит равен нулю.

Многих программистов расстраивает такое поведение сдвига вправо, и они хотели бы, чтобы число всегда дополнялось нулевыми битами. Поэтому в Java добавили еще один оператор сдвига вправо.

Записывается он так:

a >>> b

Где, а — число, биты которого сдвигаем, а b — число, означающее, на сколько бит вправо нужно сдвинуть биты числа a. Этот оператор всегда дописывает слева нулевые биты, независимо от того, какой бит знака был у числа a.



3. Работа с флагами

На основе побитовых операций и операций сдвига программисты реализовали чуть ли не целое новое направление — работа с флагами.

Во времена, когда памяти в компьютерах было очень мало, было очень популярно впихивать в одно число кучу информации. При этом число рассматривалось как массив бит: int — это 32 бита, long — 64 бита.

В такое число можно много всего записать, особенно если нужно хранить логическую информацию: true или false. Один long — это как массив boolean на 64 элемента. Такие биты назывались флагами и нуждались в операциях:

  • установить флаг
    (сделать определенный бит равным 1)
  • сбросить флаг
    (сделать определенный бит равным 0)
  • проверить флаг
    (проверить, чему равен определенный бит)

И вот как это делается с помощью побитовых операторов.

Установка флага

Чтобы установить определенный бит в 1, нужно выполнить операцию побитового ИЛИ между числом, в котором нужно установить этот бит и специальным числом, где только этот бит равен 1.

Например, вам нужно в числе 0b00001010 установить 5-й бит в 1, тогда нужно сделать так:

0b00001010 | 0b00010000 = 0b00011010

Если 5-й бит уже был установлен в единицу, ничего бы не поменялось.

В общем виде операцию установки флага можно записать так

a | (1 << b)

Где a — это число, внутри которого определенный бит устанавливается в 1. А b — это индекс устанавливаемого бита. Очень удобно использовать тут сдвиг влево – сразу видно, с каким битом мы работаем.

Сброс флага

Чтобы сбросить определенный бит в 0 и не потревожить другие биты, нужно выполнить операцию & между числом, в котором нужно сбросить бит в 0 и специальным числом, где все биты равны 1, кроме заданного.

Например, вам нужно в числе 0b00001010 установить 4-й бит в 0, тогда нужно сделать так:

0b00001010 & 0b11110111 = 0b00000010

Если 4-й бит уже был сброшен в ноль, ничего бы не поменялось.

В общем виде операцию сброса флага можно записать так

a & ~(1 << b)

Где a — это число, внутри которого сбрасывается в 0 определенный бит. А b — это индекс сбрасываемого бита.

Чтобы получить число, у которого все биты равны 1, а нужный нам равен нулю, мы сначала сдвигаем единицу на b позиций влево, а затем инвертируем биты числа с помощью побитовой операции НЕ.

Проверка флага

Кроме установки или сброса определенного флага, иногда бывает нужно просто проверить, установлен ли данный флаг – равен ли определенный бит 1. Это достаточно легко сделать с помощью побитовой операции &.

Например, вам нужно в числе 0b00001010 проверить, установлен ли 4-й бит в 1. Тогда нужно сделать так:

if ( (0b00001010 & 0b00001000) == 0b00001000 )

В общем виде операцию проверки флага можно записать так:

(a & (1 << b)) == (1 << b)

Где a — это число, внутри которого находится проверяемый бит. А b — это индекс проверяемого бита.


4. Шифрование

Побитовая операция XOR часто применяется программистами для самого простого шифрования. В общем виде операция шифрования выглядит так:

результат = число ^ пароль;

Где число — это данные, которые мы хотим зашифровать, пароль — специальное число, используемое в качестве «пароля» к данным, а результат — зашифрованное число.

число == (число ^ пароль) ^ пароль;

Все дело в том, что оператор XOR, дважды примененный к числу, дает исходное число, независимо от пароля.

Чтобы получить искомое число из зашифрованного, нужно просто выполнить операцию повторно:

оригинальное число = результат ^ пароль;

Пример:

class Solution
{
   public static int[] cript(int[] data, int password)
   {
     int[] result = new int[data.length];
     for (int i = 0; i <  data.length; i++)
       result[i] = data[i] ^ password;
     return result;
   }

   public static void main(String[] args)
   {
     int[] data =  {1, 3, 5, 7, 9, 11};
     int password = 199;

     // зашифровываем массив данных
     int[] encrypted = cript(data, password) ;
     System.out.println(Arrays.toString(encrypted));

     // расшифровываем массив данных
     int[] decrypted = cript(encrypted, password) ;
     System.out.println(Arrays.toString(decrypted));
   }
}