golang位运算那些事

前言

写这篇文章是因为看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。分别代表是否被锁住,是否唤醒,是否处于饥饿模式。至于实现原理则不在本文讨论范围内。

updatedupdated2023-08-022023-08-02