功能说明
当标识是连续的整数编号时,用每个比特表示一个元素比哈希集合更省内存,判定也只是一次位运算。这个位集合以 uint64 切片为底层,容量向上取整到 64 的倍数,适合权限位、活跃用户编号、布隆过滤器底层数组等场景。
除了单点读写,示例还提供并集、交集、计数和有序遍历。计数使用 math/bits 的 OnesCount64,遍历用 TrailingZeros64 逐个取出最低位的 1,因此结果天然按编号升序排列,不需要额外排序。
关键代码
Set 和 Clear 先用下标除以 64 定位字,再用对 64 取余得到位偏移;置位用按位或,清位用按位与非。Has 判断对应位是否为 1。Union 选择两个集合中较长的长度,缺失的字按零处理;Intersect 选择较短长度,逐字相与。Slice 只输出值为 1 的位置,并在循环中把已取出的位清零,避免死循环。
使用注意
容量在构造时确定,写入超出容量的下标会触发切片越界 panic,调用方需要自行校验编号范围。位集合不存储原始元素,只能表示非负整数。清位和置位是原地修改,多协程同时写入必须加锁。并集和交集返回新切片,不会修改输入。容量为零的集合在并集时会采用另一侧长度,结果仍然正确。序列化时注意字序,跨语言读取要约定端序。
验证方式
容量八十位时内部占用两个字,即一百二十八位。依次置位 1、5、64、79 后计数为四,查询 64 返回 true,查询 65 返回 false,遍历输出 1、5、64、79。与包含 5 和 9 的集合求并集得到五个元素,交集只包含 5。清除 64 后计数变为三,说明清位正确。
代码示例
package main
import (
"fmt"
"math/bits"
)
type Bitset []uint64
func NewBitset(size int) Bitset {
if size < 0 {
panic("negative bitset size")
}
return make(Bitset, (size+63)/64)
}
func (b Bitset) Set(index int) {
b[index/64] |= uint64(1) << (uint(index) % 64)
}
func (b Bitset) Clear(index int) {
b[index/64] &^= uint64(1) << (uint(index) % 64)
}
func (b Bitset) Has(index int) bool {
return b[index/64]&(uint64(1)<<(uint(index)%64)) != 0
}
func (b Bitset) Union(other Bitset) Bitset {
size := len(b)
if len(other) > size {
size = len(other)
}
out := make(Bitset, size)
for i := range out {
var left, right uint64
if i < len(b) {
left = b[i]
}
if i < len(other) {
right = other[i]
}
out[i] = left | right
}
return out
}
func (b Bitset) Intersect(other Bitset) Bitset {
size := len(b)
if len(other) < size {
size = len(other)
}
out := make(Bitset, size)
for i := range out {
out[i] = b[i] & other[i]
}
return out
}
func (b Bitset) Count() int {
total := 0
for _, word := range b {
total += bits.OnesCount64(word)
}
return total
}
func (b Bitset) Slice() []int {
out := make([]int, 0, b.Count())
for wordIndex, word := range b {
for word != 0 {
bit := bits.TrailingZeros64(word)
out = append(out, wordIndex*64+bit)
word &^= uint64(1) << uint(bit)
}
}
return out
}
func main() {
active := NewBitset(80)
for _, id := range []int{1, 5, 64, 79} {
active.Set(id)
}
blocked := NewBitset(80)
blocked.Set(5)
blocked.Set(9)
fmt.Println("count:", active.Count())
fmt.Println("has 64:", active.Has(64), "has 65:", active.Has(65))
fmt.Println("members:", active.Slice())
fmt.Println("union count:", active.Union(blocked).Count())
fmt.Println("intersect:", active.Intersect(blocked).Slice())
active.Clear(64)
fmt.Println("after clear:", active.Count())
}