目录

位运算实战:从 Bitmap 索引到高性能权限系统

位运算不只是面试题。从 Redis 的 Bitmap、Kafka 的 ACL 到 Linux 的文件权限,位运算是高性能系统的基础。这篇文章通过实战案例,展示位运算在真实系统中的应用。

一、位运算快速回顾

1.1 基本操作

操作符号示例结果
AND&1010 & 11001000
OR|1010 | 11001110
XOR^1010 ^ 11000110
NOT~~10100101
左移«0001 « 20100
右移»1000 » 20010

1.2 常用技巧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 设置第 n 位为 1
x |= (1 << n)

// 清除第 n 位
x &= ^(1 << n)

// 切换第 n 位
x ^= (1 << n)

// 检查第 n 位
(x >> n) & 1

// 获取最低位的 1
x & (-x)

// 清除最低位的 1
x & (x - 1)

二、案例一:Linux 文件权限

2.1 权限模型

1
2
3
4
5
6
7
8
9
-rwxr-xr--  1 user group  4096 Dec 10 10:00 file.txt
 ^^^
 421 (owner: read + write + execute = 7)
    ^^^
    421 (group: read + execute = 5)
       ^^^
       421 (others: read = 4)

# 权限值: 754

2.2 Go 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type Permission uint32

const (
    PermRead    Permission = 1 << 0  // 001 = 1
    PermWrite   Permission = 1 << 1  // 010 = 2
    PermExecute Permission = 1 << 2  // 100 = 4
)

// 检查权限
func (p Permission) Has(perm Permission) bool {
    return p&perm == perm
}

// 添加权限
func (p *Permission) Grant(perm Permission) {
    *p |= perm
}

// 移除权限
func (p *Permission) Revoke(perm Permission) {
    *p &= ^perm
}

// 使用示例
func main() {
    var perm Permission = PermRead | PermWrite  // 011 = 3
    
    fmt.Println(perm.Has(PermRead))     // true
    fmt.Println(perm.Has(PermExecute))  // false
    
    perm.Grant(PermExecute)
    fmt.Printf("%03b\n", perm)  // 111
    
    perm.Revoke(PermWrite)
    fmt.Printf("%03b\n", perm)  // 101
}

三、案例二:RBAC 权限系统

3.1 场景分析

假设一个 SaaS 系统有以下权限:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const (
    PermViewDashboard   uint64 = 1 << 0
    PermEditProfile     uint64 = 1 << 1
    PermCreateProject   uint64 = 1 << 2
    PermDeleteProject   uint64 = 1 << 3
    PermInviteUser      uint64 = 1 << 4
    PermRemoveUser      uint64 = 1 << 5
    PermManageBilling   uint64 = 1 << 6
    PermAccessAdmin     uint64 = 1 << 7
    // ... 最多 64 种权限
)

// 预定义角色
const (
    RoleViewer uint64 = PermViewDashboard
    
    RoleEditor uint64 = PermViewDashboard | 
                        PermEditProfile | 
                        PermCreateProject
    
    RoleAdmin uint64 = PermViewDashboard | 
                       PermEditProfile | 
                       PermCreateProject | 
                       PermDeleteProject |
                       PermInviteUser | 
                       PermRemoveUser
    
    RoleOwner uint64 = 0xFFFFFFFFFFFFFFFF  // 全部权限
)

3.2 高性能权限检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type User struct {
    ID          int64
    Permissions uint64  // 只需 8 字节存储全部权限!
}

// O(1) 权限检查
func (u *User) Can(perm uint64) bool {
    return u.Permissions&perm == perm
}

// 检查多个权限(全部满足)
func (u *User) CanAll(perms ...uint64) bool {
    var required uint64
    for _, p := range perms {
        required |= p
    }
    return u.Permissions&required == required
}

// 检查多个权限(满足任一)
func (u *User) CanAny(perms ...uint64) bool {
    var required uint64
    for _, p := range perms {
        required |= p
    }
    return u.Permissions&required != 0
}

3.3 与传统实现对比

传统方式(关系表)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 查询用户权限
SELECT p.name 
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = ?

-- 检查权限
SELECT COUNT(*) 
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id
WHERE ur.user_id = ? AND rp.permission_id = ?

性能对比

方面关系表Bitmap
存储3 张表 + 索引1 个 uint64 字段
检查权限JOIN 查询位运算 O(1)
添加权限INSERTOR 运算
网络开销多次 DB 往返无(已加载到内存)

3.4 数据库存储

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 存储到数据库
func (u *User) Save(db *sql.DB) error {
    _, err := db.Exec(
        "UPDATE users SET permissions = ? WHERE id = ?",
        u.Permissions,  // 直接存 uint64
        u.ID,
    )
    return err
}

// 从数据库加载
func LoadUser(db *sql.DB, id int64) (*User, error) {
    var u User
    err := db.QueryRow(
        "SELECT id, permissions FROM users WHERE id = ?",
        id,
    ).Scan(&u.ID, &u.Permissions)
    return &u, err
}

四、案例三:Bitmap 索引

4.1 场景:用户标签系统

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 用户标签(最多 64 种)
const (
    TagPremium      uint64 = 1 << 0
    TagVerified     uint64 = 1 << 1
    TagNewUser      uint64 = 1 << 2
    TagHighValue    uint64 = 1 << 3
    TagChurningRisk uint64 = 1 << 4
    // ...
)

type UserIndex struct {
    // 每个标签对应一个 bitmap
    // bitmap[i] 的第 j 位表示第 j 个用户是否有标签 i
    bitmaps [64][]uint64
    userCount int
}

4.2 快速筛选

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 找出所有 "Premium AND Verified AND NOT ChurningRisk" 的用户
func (idx *UserIndex) Query(must, mustNot uint64) []int {
    var result []int
    
    // 计算每个 block 的匹配情况
    blocks := (idx.userCount + 63) / 64
    for b := 0; b < blocks; b++ {
        var match uint64 = 0xFFFFFFFFFFFFFFFF
        
        // AND 条件
        for i := 0; i < 64; i++ {
            if must&(1<<i) != 0 {
                match &= idx.bitmaps[i][b]
            }
        }
        
        // NOT 条件
        for i := 0; i < 64; i++ {
            if mustNot&(1<<i) != 0 {
                match &= ^idx.bitmaps[i][b]
            }
        }
        
        // 提取匹配的用户 ID
        for match != 0 {
            pos := bits.TrailingZeros64(match)
            result = append(result, b*64+pos)
            match &= match - 1  // 清除最低位的 1
        }
    }
    
    return result
}

4.3 性能分析

假设 1000 万用户,64 个标签:

方案存储查询复杂度
传统表10M × 64 行O(n) 扫描
Bitmap64 × 1.25MB = 80MBO(n/64) 位运算

实际速度:现代 CPU 一次可以处理 64 位,配合 SIMD 可以更快。

五、案例四:布隆过滤器

5.1 原理

布隆过滤器用多个 hash + bitmap 实现"可能存在"的高效判断:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type BloomFilter struct {
    bits    []uint64
    numHash int
}

func (bf *BloomFilter) Add(item string) {
    for i := 0; i < bf.numHash; i++ {
        hash := bf.hash(item, i)
        idx := hash / 64
        bit := hash % 64
        bf.bits[idx] |= 1 << bit
    }
}

func (bf *BloomFilter) MayContain(item string) bool {
    for i := 0; i < bf.numHash; i++ {
        hash := bf.hash(item, i)
        idx := hash / 64
        bit := hash % 64
        if bf.bits[idx]&(1<<bit) == 0 {
            return false  // 一定不存在
        }
    }
    return true  // 可能存在
}

5.2 应用场景

  • Redis:判断 key 是否存在
  • 数据库:跳过不含目标数据的 SSTable
  • 爬虫:URL 去重
  • 推荐系统:过滤已推荐内容

六、实战技巧

6.1 超过 64 种权限

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type LargePermission struct {
    bits []uint64
}

func (p *LargePermission) Set(n int) {
    idx := n / 64
    bit := n % 64
    // 动态扩容
    for len(p.bits) <= idx {
        p.bits = append(p.bits, 0)
    }
    p.bits[idx] |= 1 << bit
}

func (p *LargePermission) Has(n int) bool {
    idx := n / 64
    bit := n % 64
    if idx >= len(p.bits) {
        return false
    }
    return p.bits[idx]&(1<<bit) != 0
}

6.2 原子操作(并发安全)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import "sync/atomic"

type AtomicPermission struct {
    bits uint64
}

func (p *AtomicPermission) Grant(perm uint64) {
    for {
        old := atomic.LoadUint64(&p.bits)
        new := old | perm
        if atomic.CompareAndSwapUint64(&p.bits, old, new) {
            return
        }
    }
}

func (p *AtomicPermission) Has(perm uint64) bool {
    return atomic.LoadUint64(&p.bits)&perm == perm
}

6.3 序列化为 JSON

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type Permission uint64

func (p Permission) MarshalJSON() ([]byte, error) {
    // 转为权限名列表,更可读
    names := []string{}
    if p&PermRead != 0 {
        names = append(names, "read")
    }
    if p&PermWrite != 0 {
        names = append(names, "write")
    }
    // ...
    return json.Marshal(names)
}

七、总结

应用场景位运算优势
权限系统O(1) 检查,8 字节存储 64 种权限
标签/分类快速筛选,支持复合条件
布隆过滤器空间效率极高的存在性判断
状态机多状态并存的紧凑表示

核心收获

  1. 位运算把 CPU 指令级优化带入应用层
  2. 空间压缩 + 时间常数 = 双赢
  3. 适合"多选一"或"多选多"的场景
  4. 配合原子操作可实现无锁并发