JavaRush /Курсы /Java Multithreading /Практическое применение (Битовые маски,...)

Практическое применение (Битовые маски,...)

Java Multithreading
10 уровень , 10 лекция
Открыта

— Привет, Амиго!

Еще хотел бы рассказать про битовые маски и XOR.

Ты уже знаешь, что числа состоят из битов и над этими битами можно производить различные операции. Битовая маска – это когда мы храним много различных логических значений (true/false) в виде одного целого числа. При этом каждому boolean-значению соответствует определенный бит. Вот как это можно делать:

Числа-степени двойки (1,2,4,8,16,32,…) занимают в двоичном представлении числа всего по одному биту:

Число Двоичное представление
1 0000 0001
2 0000 0010
4 0000 0100
8 0000 1000
16 0001 0000
19 (не степень двойки) 0001 0011
31 (не степень двойки) 0001 1111

Поэтому любое целое число можно рассматривать, как массив бит или массив boolean.

Вот как можно хранить различные логические значения в одном числе:

Значения логических переменных
boolean a = true;
boolean b = false;
boolean c = true;
boolean d = false;
Упаковка их в одно число:
int result = 0;
 if (a) result += 1; //1 == 20 — нулевой бит
 if (b) result += 2; //2 == 21 — первый бит
 if (c) result += 4; //4 == 22 — второй бит
 if (d) result += 8; //8 == 23 — третий бит

Теперь каждый бит равен 1, если соответствующая ему логическая переменная была true.

В нашем случае true были переменные a и c, значит число result равно 1+4 ==5

0000 0101
0000 dcba

— Вроде понятно, что происходит.

— Ну, раз понятно, то пошли дальше.

В числе int 32 бита, один из них используется для знака, а еще 31 можно использовать для хранения значений 31-й булевской переменной.

— А в числе long 64 бита. Мы там можем хранить 63 логических переменных.

— Ага.

— Полсотни переменных в одном числе. Немало.

А где такое применяется?

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

Именно хранить. Т.к. пользоваться им в работе не так уж удобно.

— Кстати, как раз хотел спросить. А как нам обратно получить логические значение их числа?

— Это совсем не сложно. Вот допустим тебе нужно узнать, установлен ли 5-й бит числа в единицу или нет (2 в пятой степени – это 32). Вот как это можно проверить:

Соединяем числа в одно:
int a = 32; //25 == 0010 0000
int b = 8; //23 == 0000 1000
int c = 2; //21 == 0000 0010

int result = a+b+c; //32+8+2 == 42 == 0010 1010
Восстанавливаем обратное значение – проверяем установлен ли определенный бит:
int a = result & 32; // 0010 1010 & 0010 0000 = 0010 0000
int b = result & 8; // 0010 1010 & 0000 1000 = 0000 1000
int c = result & 2; // 0010 1010 & 0000 0010 = 0000 0010

Т.е. фактически, для работы с битовыми масками нужны три операции:

1) Установить определенный бит в 0

2) Установить определенный бит в 1

3) Проверить, какое значение определенного бита.

Возьмем, например, бит номер 6.

Как установить в 1 бит 6?

Код Описание
result = result | 01000000;
result |= 01000000;
Как установить в 1 бит 6?
result = result & 10111111;
result &= 10111111;
Как установить в 0 бит 6?
с = result & 01000000;
Как получить значение 6-го бита?

— Очень необычно, но не сложно. Я ж теперь уже крутой программист.

— И теперь еще маленькая подсказка. Как легко получить числа со снятым или установленным определенным битом: 01000000 или 10111111.

Для этого у нас есть операции сдвига >> и <<.

Вот 1 – это 2 в нулевой степени. Т.е. число с установленным нулевым битом. Нам надо число с установленным 6-м битом.

int c = 1<<6; //0000 0001 <<6 == 0100 0000 == 64

— Круто! Действительно полезная вещь для таких дел.

А если мне нужно число, где все биты 1, а один определенный – 0?

— Это тоже не сложно:

int d = ~(1<<6); //~0100 0000 == 10111111

Т.е. все очень просто:

Код Описание
result = result | (1<<6);
result |= (1<<6);
Как установить в 1 бит 6?
result = result & ~(1<<6);
result &= ~(1<<6);
Как установить в 0 бит 6?
с = result & (1<<6);
Как получить значение 6-го бита?

— Выглядит не очень сложно. Но так сразу — не запомню.

— Зато, если встретишь где-то в чужом коде такое страшное выражение:

«result &= ~(1<<6)», будешь знать, что это кто-то просто работает с битовой маской. А если часто будешь встречать, то вскоре все само запомнится.

— Само запомнится… Хорошо звучит. Спасибо за лекцию.

Комментарии (91)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Long_byte Уровень 54
30 июля 2024
все стало чуть понятно но не понятно где это все использовать
Алексей Уровень 45
20 мая 2025
в задачах в позапрошлом уроке, например!)
Denis Odesskiy Уровень 47
27 июля 2024
Ну допустим, так можно?

package com.testall.bitmask;

public class AccessControl {
    public static void main(String[] args) {
        User user = new User();

        // Устанавливаем права доступа юзеру на чтение и запись.
        System.out.println("Установленны права доступа пользователю к файловой системе:");
        user.grantPermission(Permissions.READ);
        user.grantPermission(Permissions.WRITE);

        user.printPermissions(); // Тут будет число 11 (0011 в двоичной системе (или 3 в десятичной).

        System.out.println("Has READ: " + user.hasPermission(Permissions.READ)); // true
        System.out.println("Has WRITE: " + user.hasPermission(Permissions.WRITE)); // true
        System.out.println("Has EXECUTE: " + user.hasPermission(Permissions.EXECUTE)); // false
        System.out.println("Has DELETE: " + user.hasPermission(Permissions.DELETE)); // false
        System.out.println();

        // Отзываем права доступа у юзера на запись.
        System.out.println("Отзываем права доступа у юзера к файловой системе:");
        user.revokePermission(Permissions.WRITE); 

        System.out.println("Has READ: " + user.hasPermission(Permissions.READ)); // true
        System.out.println("Has WRITE: " + user.hasPermission(Permissions.WRITE)); // false
        System.out.println("Has EXECUTE: " + user.hasPermission(Permissions.EXECUTE)); // false
        System.out.println("Has DELETE: " + user.hasPermission(Permissions.DELETE)); // false

        user.printPermissions(); // Тут будет число 1 (0001 в двоичной системе (или 1 в десятичной).
    }
}
Denis Odesskiy Уровень 47
27 июля 2024

class Permissions {
    public static final int READ = 1 << 0; // 0001
    public static final int WRITE = 1 << 1; // 0010
    public static final int EXECUTE = 1 << 2; // 0100
    public static final int DELETE = 1 << 3; // 1000
}

class User {
    private int permissions = 0;

    public void grantPermission(int permission) {
        permissions |= permission;
    }

    public void revokePermission(int permission) {
        permissions &= ~permission;
    }

    public boolean hasPermission(int permission) {
        return (permissions & permission) != 0;
    }

    public void printPermissions() {
        System.out.println("Current permissions: " + Integer.toBinaryString(permissions));
    }
}
Вывод:

Установленны права доступа пользователю к файловой системе:
Current permissions: 11
Has READ: true
Has WRITE: true
Has EXECUTE: false
Has DELETE: false

Отзываем права доступа у юзера к файловой системе:
Has READ: true
Has WRITE: false
Has EXECUTE: false
Has DELETE: false
Current permissions: 1

Process finished with exit code 0
19 мая 2024
У друга спросил который уже 5 лет java разработчик, понадобились ли ему когда-нибудь ПОБИТОВЫЕ операции. Ответ: НИКОГДА!
AnnE Уровень 51
1 июля 2024
спасибо, вы немного подняли мой боевой дух, который упал под плинтус после битовых задач 🥶
Михаил Уровень 32
6 апреля 2023
И вот, что понял Петька из слов Василииваныча

result |= (1<<6);
В массиве, где хранятся настройки вкл/выкл, в седьмой настройке справа мы сделали ВКЛ (то есть включили) (седьмой потому что считается с нуля первая - нулевая позиция)

result &= ~(1<<6);
а вот так - то же самое выключили

с = result & (1<<6);
а так узнали оно включено или нет. осталось выяснить - откуда брать result.... ааааа, result и есть - массив с настройками, куда уже загнали все нужные нам настройки и что мы будем делать с переменной с в последнем выражении? получили, понимаете ли, 64 - что это нам говорит? 2^6 = 64 То есть если 64 получили, а не ноль, то значит там (на 6й позиции) ВКЛ, а если ноль, то ВЫКЛ А зачем нам все эти степени? А чтобы в ряду цифирь при выяснении вкл или выкл стояла одна единица, а ее положение и указывает на настройку. А такое дать может только 2^1, 2^2, 2^3 ..... Без комментариев первопроходцев ничего бы не понял, а пошел бы и самоубился....
Ra Уровень 35 Student
12 июля 2023
Это просто базы нет, обычной институтской булевой алгебры и популярных книжечек по ассемблеру))
IrinaHonya Уровень 45
7 февраля 2023
Эти биты для меня как нечто инопланетное ☹ туго их понимаю Это реально нужно в работе или для ознакомления 🤔
Igor Уровень 38
13 ноября 2022
https://javarush.com/quests/lectures/questsyntaxpro.level08.lecture05
Gans Electro Уровень 4
30 июня 2023
Igor Petrashevsky Уровень 47
4 августа 2022
битовых полей нету чтоли? колхозить как на ассемблере?
Anutka Уровень 42
6 июля 2022
Как установить в 1 бит 6? Поняла эту фразу, благодаря комменту Maksim Yugai. А еще result = result | (1<<6); тоже долго вдупляла, что такое result, откуда взяли😂
Baggins Уровень 48
22 июня 2022
честно очень тяжело, вернусь с утра....
LuneFox Уровень 41 Expert
3 декабря 2021
Я определённо встречал это уже где-то во время прохождения курса. В каком-то классе устанавливал нужный набор флагов через int, который на самом деле и являлся набором из отдельных по смыслу битов. А ещё в Linux права доступа могут быть записаны целым числом, вместо того, чтобы указывать каждый флаг вручную. Полагаю, именно так они и хранятся, потому что учитывая количетво файлов в операционной системе хранить права доступа в другой форме было бы расточительно: