位运算、位图法以及状态管理

背景

前几天读某个框架的代码,一路下来读着挺顺畅的,唯有读到某处的时候卡顿了,这个地方主要判断是否属于某种状态,但是有点不直观,主要是它使用了位运算中的或运算。虽然不是很直观,但是觉得特别的,因为平常就是四则运算比较多,涉及位运算确实少。于是记了下来,后来回忆其实JDK里面也有不少涉及位运算来管理判断状态的,比如JDK的线程池就有使用。

位运算

在 Java 语言中,位运算有如下这些:

  • 左移(<<)
  • 右移(>>)
  • 无符号右移(>>>)
  • 与(&)
  • 或(|)
  • 非(~)
  • 异或(^)

比较常用的有:

  • &(与运算):只有当两方都为 true 时,结果才是 true,否则为 false。
  • |(或运算):只要当一方为 true 时,结果就是 true,否则为 false。
  • ^(异或运算):只要两方不同,结果就是 true,否则为 false。

一个位运算的例子

操作数 5和3
在 Java 中,int 占了 4 个字节(Byte),一个字节呢又等于 8 个 Bit 位。

5 和3的二进制形式

5
0000 0101
3
0000 0011

与运算

5 & 3

0000 0101
0000 0011
-------------
0000 0001

运算结果:5 & 3 = 1

或运算

5 | 3

0000 0101
0000 0011
-------------
0000 0111

运算结果:5 | 3 = 7

异或运算

5 ^ 3

0000 0101
0000 0011
-------------
0000 0110

异或运算结果:5 ^ 3 = 6

应用

定义一组2的n次幂的数,用来标志不同的状态位。

例如:
public static final int ROLE_NORMAL = 0;
public static final int ROLE_USER = 1;
public static final int ROLE_ADMIN = 2;
public static final int ROLE_SUPER_ADMIN = 4;
public static final int ROLE_SUPER_SUPER_ADMIN = 8;

位数值
含义 8 4 2 1 0
0000 0000 0000 0000
0000 0000 0000 0001
0000 0000 0000 0010
0000 0000 0000 0100
0000 0000 0000 1000

定义一个变量,用于存储状态(默认值是 0):

private static int roleStatus = ROLE_NORMAL;

当我们要保存状态时,直接用 | 运算即可:

roleStatus = roleStatus | ROLE_USER;

保存的运算过程如下:

0000 0000
0000 0001
--------------
0000 0001

运算结果:roleStatus=1,相当于就把这个 1 存储到 0 的二进制当中了。

** 那么如果要判断 roleStatus 中是否有某个状态呢?使用 & 运算:

roleStatus & ROLE_USER) != 0 // true,代表有;false代表无

计算过程:
roleStatus此时为:roleStatus= 1

0000 0001
0000 0001
-------------
0000 0001

即:roleStatus & ROLE_USER) = 1,因此 roleStatus & ROLE_USER) != 0 ,持有该状态(具有ROLE_USER权限)。

再来判断一个不存在的状态 roleStatus & ROLE_SUPER_ADMIN:
此时:roleStatus= 1

System.out.println((roleStatus & ROLE_SUPER_ADMIN) != 0);// false,代表没有它

计算过程:

0000 0001
0000 0100
------------
0000 0000

计算结果:roleStatus & ROLE_SUPER_ADMIN = 0,说明尙不是ROLE_SUPER_ADMIN。

删除某种状态

roleStatus已经存储了ROLE_USER状态,那如何删除呢?可以使用异或运算

roleStatus ^= ROLE_USER

计算过程:

0000 0001
0000 0001
---------------
0000 0000

roleStatus ^= ROLE_USER的结果为0,又回到了初始状态,即删除了该状态。

又一个例子:
具有ROLE_USER、ROLE_ADMIN、ROLE_SUPER_ADMIN三种状态的例子:

roleStatus = ROLE_USER | ROLE_ADMIN | ROLE_SUPER_ADMIN ;

计算过程:

0000 0000
0000 0001
0000 0010
0000 0100
-------------
0000 0111

计算结果为:7。(看上去是不是很像Linux的用户权限状态码?Linux的权限也是一样的设计。)

去除ROLE_SUPER_ADMIN状态,计算过程:
roleStatus 此时为roleStatus=7(0000 0111)
roleStatus ^= roleStatus | ROLE_SUPER_ADMIN;

0000 0111
0000 0100
-------------
0000 0011

计算结果:roleStatus = 3,已经去除ROLE_SUPER_ADMIN状态。

最后

通过 |、^、& 运算,可以很方便快捷的对状态值进行操作。在某些场景下,如果使用位操作来管理,能简化很多操作,还很节省存储。
(完)

发表评论

邮箱地址不会被公开。 必填项已用*标注