一、前言

  1. 最近在工作中、技术问答、开源库中常见到不少的位操作。
  2. 但是却发现蛮多人知道有位操作,但是应用于业务项目中的很少,经常是见到没有反应过来。
  3. 这里我介绍一些位操作的在业务中的使用,希望大家在见到位操作时能够想起这是什么。

二、位操作基本原理

这里不多介绍位操作,这篇文章也只会用到 |& 的操作。

1). 或(|)操作

拆解后为对两个数的相同二进制位进行与操作,及 以下四种情况:

  1. 1 | 1 == 1
  2. 1 | 0 == 1
  3. 0 | 1 == 1
  4. 0 | 0 == 0

及只有都为 0 才会等于 0,其它情况都为 1

5 | 2 == 7

1
2
3
4
5
6
7
8
9
 +-----------------------+-----------------------+
 |  0000 0000 0000 0000  |  0000 0000 0000 0101  |
 +-----------------------+-----------------------+
 |  0000 0000 0000 0000  |  0000 0000 0000 0010  |
 +-----------------------+-----------------------+
                         =
 +-----------------------+-----------------------+
 |  0000 0000 0000 0000  |  0000 0000 0000 0111  |
 +-----------------------+-----------------------+

2). 与(&)操作

拆解后为对两个数的相同二进制位进行与操作,及 以下四种情况:

  1. 1 & 1 == 1
  2. 1 & 0 == 0
  3. 0 & 1 == 0
  4. 0 & 0 == 0 及只有都为 1 才会等于 1,其它情况都为 0

一般用于判断这个数是否包含相同位的情况:

1
2
3
4
5
6
1 & 1 == 1
3 & 1 == 1
2 & 1 == 0
7 & 8 == 0
7 & 3 == 3
7 & 9 == 1
1
2
3
4
// Has & 操作后只有完全相同才是包含全部
func Has(flags , v int) bool {
    return flags & v == v
}

三、在实际业务中的应用

1). IPv4 地址存储传输

IPv4 的地址为 1-255.1-255.1-255.1-255 真正的有效存储为 4byte 一个 byte 刚好存放的下一个,c 中的实现使用的 uint32 类型来做的,golang 则使用 []byte 来做的。

2). 在数据库中使用 uint32 存放一个月的签到记录

这个操作当初也是在一个问答站上看见的,觉得一下子拓宽了使用位操作的思路,而且 mysql 也是支持位操作的筛选的,32 个位可存放 32 个 true/false 的记录并且是有顺序的,可以说是个长度为 32 的 boolean 数组,用来作为一个月的签到记录也是刚刚好了。

3). 有限的作为多选结果存放

我现在的项目中有大量的应用到这种方案,比如一个属性为多选,但是数量有限在 31 个以内就可以考虑这个方案了。

4). 在实际的编程中作为多选标记属性

infernojs 中有大量应用这个方案来简化判断可以见 inferno.js 为什么这么快

5). 网络的通信协议中使用

比较出名的 ipmsg 就是大量的使用位操作把每个位作为各个设置,比如是否加密之类的。

四、一些比较喜欢用的位操作封装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 遍历所有位
 */
function forEachBit(v, handle) {
    // 通过log算数方法取出最高位在多少
    const count = Math.log2(v);
    let i = count;
    for (let i = count; i >= 0 && v; i--) {
        const item = 1 << i;
        if (v & item) {
            // 优化当所有位遍历完就会主动退出循环
            v -= item;
            handle(item);
        }
    }
}
/**
 * 遍历所有位,作为一个数组返回
 */
function toArrayBit(v) {
    const arr = [];
    forEach(v, i => arr.push(i));
    return arr;
}

四、开放给他人使用

当你有一个位操作的数据,不应该直接开放使用,而是把现有的操作封装为方法让接入人能够无障碍接入。

例如 http2 的 Flag 的操作,完全的把位操作封装起来。

1
2
3
4
5
type Flags uint8

func (f Flags) Has(v uint8) bool {
    return (f & v) == v
}

五、参考

  1. inferno.js 为什么这么快
  2. go-http2