23 февраля 2010

Использование целочисленного типа данных для размещения логических переменных

Представим себе что у нас есть некоторый объект, который описывается рядом логических параметров (флагов). Например объект у нас будет "спорткомплекс" , а параметрами
1) наличие тренажёрного зала - да/нет
2) наличие басейна - да/нет
3) наличие фитнес центра - да/нет
4) наличие футзала - да/нет
5) наличие боксёрского ринга - да/нет
и т.д.
Задача довольно проста, найболее эффективным образом хранить эти все параметры.
Самым тривиальным является вариант создания 5 булевых членов нашего класса, каждый из которых будет отвечать за свой параметр. Кроме простоты реализации, в памяти эти параметры для каждого экземпляра класса будут занимать всего лишь по 5 бит (1 boolean = 1 bit). Но, что произойдёт если надо передавать такую информацию по сети, да ещё и в более менее осмысленном виде, например в хмл. Каждый из параметров обростёт дополнительными тегами, в результате чего его размер возрастёт в несколько раз. Более того, формирование такого хмл самого по себе потребует определённых ресурсов. Необходимо найти более оптимальный метод хранения этих флагов.
Для решение этой задачи можно воспользоваться типом данных int. Как известно, целочисленный тип данных в джаве состоит из 4 байт - 32 бит. Если представить int в двоичном формате, то это будет ряд из 32х нулей и единиц, а это значит что мы можем разместить в нём аж 32 логических переменных. Для оптимального сохранения boolean переменных в int переменной удобно воспользоваться операцией побитового сдвига.
Например, для размещения флагов из выше изложенного примера, мы воспользуемся первыми 6тью битами целого числа (1й бит будет рабочим, и флаг в нём храниться не будет).
Итак, для примера, что-бы сохранить первые два флага в общей int переменной можно воспользоватся следующим кодом:

// set flag 1
int flag1 = 1; // flag = 0 / 1
int flagPosition = 1;
i = i + (flag1<<flagPosition);
// set flag 2
int flag2 = 1; // flag = 0 / 1
flagPosition = 2;
i = i + (flag2<<flagPosition);
System.out.println(i);

В результате на экран выведется число 6. В двоичной системе 6 = 110, и если отбросить первый служебный 0, то видим что оба флага установлены в единицу. Данный пример можно преобразовать в унифицированную функцию, например следующего вида:
...
int flags = 0;

void setFlags(int flagValue, int flagPosition) {
flags = flags | (flagValue<<flagPosition);
}
...

Данная оптимизация не даст выиграша в памяти при хранения экземпляра класса, но даст возможность более эффективно (де)сериализировать, и пересылать набор флагов по сети.