ZEROMAKE | keep codeing and thinking!
2019-02-28 | business

位操作的应用

一、前言

  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
| 0000 0000 0000 0000 | 0000 0000 0000 0101 |
3
+-----------------------+-----------------------+
4
| 0000 0000 0000 0000 | 0000 0000 0000 0010 |
5
+-----------------------+-----------------------+
6
=
7
+-----------------------+-----------------------+
8
| 0000 0000 0000 0000 | 0000 0000 0000 0111 |
9
+-----------------------+-----------------------+

2). 与(&)操作

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

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

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

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

三、在实际业务中的应用

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

四、开放给他人使用

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

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

1
type Flags uint8
2
3
func (f Flags) Has(v uint8) bool {
4
return (f & v) == v
5
}

五、参考

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