写这篇文章是因为看golang
源码的时候,经常会出现位运算。有时看着有点懵,所以写篇文章记录下。同时该文章也会探讨我们在生产中的哪些场景可以使用位运算。
计算机的世界万物都是二进制(都是0和1),位运算本质上就是对这些0和1直接进行操作。常见的操作方式会将编程语言转换为二进制,而位运算省略了转换步骤,所以它的效率会更高。
golang
中常见的位运算有:
运算符号 |
用途 |
& |
与运算, 两个二进制都为1则结果为1, 其他情况均为0 |
| |
或运算, 只要有一个二进制为1, 结果就为1 |
^ |
异或运算, 两个二进制不同, 结果为1 |
~ |
取反运算, 0变1, 1变0 |
<< |
左移运算, 高位丢弃, 低位补0 |
>> |
右移运算, 低位丢弃, 无符号数高位补0, 有符号数高位补符号 |
tips: golang中可以使用fmt.Printf("%b", num)
得到一个变量的二进制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
func main() {
a := 10
b := 13
// output: a: 1010, b: 1101
fmt.Printf("a: %b, b: %b\n", a, b)
// 与运算
// output: 1000
fmt.Printf("%b\n", a&b)
// 或运算
// output: 1111
fmt.Printf("%b\n", a|b)
// 异或运算
// output: 0111
fmt.Printf("%b\n", a^b)
// 左移
// output: 0001 0100
fmt.Printf("%b\n", a<<1)
// 右移
// output: 0000 0101
fmt.Printf("%b\n", a>>1)
}
|
上面的代码演示了,golang
中常见的几种位运算操作
接下来看看实战中可以如何运用
1
2
3
4
5
6
7
8
9
|
func main() {
a := 10
b := 7
aResult := a & 1
bResult := b & 1
// output: a: 0, b: 1
fmt.Printf("a: %v, b: %v\n", aResult, bResult)
}
|
之前判断奇偶数可以用%
,也可以用位运算。我们用&(与运算)
。
判断奇偶数本质上就是看最后一个二进制位是0(偶数)还是1(奇数)。这里目标数和1进行&
,如果结果为0说明是偶数,反之则为奇数。
- 左移n位等价于乘以2的n次方
- 右移n位等价于乘以2的n次方
1
2
3
4
5
|
i := 1
// i * (2的1次方)
i << 1
// i * (2的2次方)
i << 2
|
读者通常可以对一篇博客进行点赞、收藏和评论的操作。
这里我们利用左移运算来定义三个常量:
1
2
3
4
5
6
7
8
|
const (
// 0000 0001
Like = 1 << iota
// 0000 0010
Collect
// 0000 0100
Comment
)
|
如果想要给某个文章添加能力,可以使用|(或运算)
:
1
2
3
|
state := Like | Collect | Comment
// output: 0000 0111
fmt.Printf("%b\n", state)
|
新赋值的变量state
就拥有了点赞、收藏、评论的状态了。
要取消某个变量的状态,可以使用^(异或运算)
:
1
2
3
|
state = state ^ Comment
// output: 0000 0011
fmt.Printf("%b\n", state)
|
使用异或之后,对应的状态位,因为二进制相同,就被置为0了(取消能力)。
校验某个变量是否具有该能力,使用&(与运算)
:
1
2
3
4
|
// output: true
fmt.Printf("%v\n", (state&Like) == Like)
// output: false
fmt.Printf("%v\n", (state&Comment) == Comment)
|
完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
const (
// 0000 0001
Like = 1 << iota
// 0000 0010
Collect
// 0000 0100
Comment
)
func main() {
state := Like | Collect | Comment
// output: 0000 0111
fmt.Printf("%b\n", state)
state = state ^ Comment
// output: 0000 0011
fmt.Printf("%b\n", state)
// output: true
fmt.Printf("%v\n", (state&Like) == Like)
// output: false
fmt.Printf("%v\n", (state&Comment) == Comment)
}
|
这里主要是用到了二进制位运算的特性。
- 左右移,利用二进制的错位,每个二进制都代表着不同的状态
- 或运算,可以给对应变量添加某种状态
- 与运算,判断变量是否具有该状态
- 异或运算,取消某个变量的状态
sync.mutex
中就用到了位运算:
1
2
3
4
5
6
7
8
9
10
|
type Mutex struct {
state int32
sema uint32
}
const (
mutexLocked = 1 << iota // mutex is locked
mutexWoken
mutexStarving
)
|
结构体中的state
就是一个4字节的整形数字。
它有3个状态: mutexLocked: 0000 0001
, mutexWoken: 0000 0010
, mutexStarving: 0000 0100
。分别代表是否被锁住,是否唤醒,是否处于饥饿模式。至于实现原理则不在本文讨论范围内。