位运算保存状态
位运算保存状态
理论
在go里面,没有提供枚举这一类型,所以我们会通过声明常量来表示一些特定的状态,比如下面这种形式
const (
A = 1 + iota
B
C
D
)
var status uint8
通过const
+ iota
的方式来定义一些状态,这样做的缺点在于,一个变量只能同时存储一个状态,如果要同时表示多个状态,就需要使用多个变量,而使用位来存储这些状态可以很好的解决这种问题,其过程只涉及到了简单的位运算。
比特位存储状态原理是每一个比特位表示一个状态,1表示拥有此状态,0表示未拥有此状态,那么总共能表示多少个状态取决于有多少个比特位,在go语言中,使用uint64
类型可以最多可以表示64个状态。在这种情况下,其所存储状态的值就有一定的要求,其值必须是2的整数次方,比如2的2次方
0b10
2的8次方
0b10000000
假设现在用一个无符号8位整数来存储这些状态,意味着可以有8个比特位可以使用,也就是uint8(0)
const (
A = 0b1 << iota
B
C
D
)
var status uint8
将其与0b10
进行或运算,或运算的符号是|
00000010
| 00000000
------------
00000010
或运算的规则是同为0取0,否则取1,进行或运算后,就可以将该状态的标志位记录到变量中。同理,也可以存储多个其它不同的状态,将上面计算的结果与0b10000000
再次进行或运算后,此时状态变量的二进制位中,已经有两个比特位为1。
10000000
| 00000010
------------
10000010
如果要一次性存储多个状态,可以先将几个状态进行或运算,再存储到状态变量中,比如一次性存储状态ABCD
00000001
| 00000010
------------
00000011
| 00000100
------------
00000111
| 00001000
------------
00001111
| 00000000
------------
00001111
最终status
的值就是
0b00001111
既然有存储状态,就肯定要读取状态,读取状态的原理同样十分简单。假如要确认状态A
是否存在于status
变量中,使用与运算&
即可,其规则为同为1取1,否则取0,由于这些状态值全都是2的正整数次方,二进制位中永远只有一个位为1,所以两者进行与运算时,只有相同的那一个比特位才能为1,其余全为0,如果计算结果为0,说明指定位不相同,则不包含此状态,计算过程如下。
00000001
& 00001111
------------
00000001
同理,如果想判断多个状态是否存在于status
中,将多个状态值进行或运算,然后将结果与status
进行与运算即可,比如下面判断是否同时包含状态ABC。
00000001
| 00000000
------------
00000001
| 00000010
------------
00000011
| 00000100
------------
00000111
& 00001111
------------
00000111
最后一个操作就是撤销状态,将指定状态从status
中删除,经过上面两个操作的讲解后相信可以很容易就能想到删除的原理。实际上有两种方法可以操作,其结果都是一样的,第一种是将指定状态取反,然后将结果与status
相与,就能得到删除指定状态后的status
。假设删除状态D
其过程如下,
~ 00001000
------------
11110111
& 00001111
------------
00000111
取反会将自身的每一个比特位反转,反转后只有一个比特位为0,也就是要删除的比特位,这样一来将与status
进行与运算,就能将指定比特位置0。另一个方法就是直接将两者进行异或运算,异或的规则是不相同取1,相同取0,计算过程如下
00001000
^ 00001111
------------
00000111
可以看得出来异或就等于取反后相与,两者是完全等价的。如果要删除多个状态,跟之前同理,多个状态进行或运算后再进行异或,比如下面删除状态ABC
00000001
| 00000000
------------
00000001
| 00000010
------------
00000011
| 00000100
------------
00000111
^ 00001111
------------
00001000
实现
理论部分讲完过后,下面看看怎么用代码来进行实现,这种操作是不限语言的,这里使用go语言来进行实现。需要注意的是,go语言中取反运算符和异或运算符是同一个,都是^
符号。
type BitFlag uint64
首先可以声明一个BitFlag
类型,其底层类型为uint64
,最多可以同时存储64个状态,在实际代码中可以直接使用位运算来进行操作,这里选择稍微封装了一下。
type BitFlag uint64
func (bf *BitFlag) Store(flags ...uint64) {
for _, flag := range flags {
*bf |= BitFlag(flag)
}
}
func (bf *BitFlag) Check(flags ...uint64) bool {
var f uint64
for _, flag := range flags {
f |= flag
}
return *bf&BitFlag(f) != 0
}
func (bf *BitFlag) Revoke(flags ...uint64) {
var f uint64
for _, flag := range flags {
f |= flag
}
*bf ^= BitFlag(f)
}
可以看到代码量非常少,实现起来也很简单,下面是一个简单的使用案例
const (
A = 0b1 << iota
B
C
D
E
F
G
)
func main() {
var flag BitFlag
flag.Store(A, B, C, D, E)
fmt.Println(flag.Check(A, E))
fmt.Println(flag.Check(F, G))
flag.Revoke(A, D)
fmt.Println(flag.Check(A, D))
}
输出
true
false
false