Go中用[]uint64+位运算实现位图,每单元存64位;索引计算为byteIndex=index>>6、bitOffset=index&63;设置位用bits[byteIndex] |= 1

Go 里怎么用 bit 操作实现位图(BitMap)
Go 没有内置 BitMap 类型,但靠 []uint64 + 位运算就能高效实现。核心是把每个 bit 当作一个布尔开关,用整数数组存状态,省空间、快访问。
常见错误是直接用 []bool —— 它每个元素占 1 字节,浪费 8 倍内存;或者手写位移时搞错索引:比如把 index / 64 写成 index >> 6 却忘了加括号,导致优先级出错。
- 用
[]uint64存储,每 64 个 bit 打包进一个单元 - 位位置计算必须用:
byteIndex := index / 64和bitOffset := index % 64(或等价的index >> 6和index & 63) - 设置 bit:
bits[byteIndex] |= 1 - 读取 bit:
(bits[byteIndex] & (1
为什么不用 math/bits 的 OnesCount64 就算不了位图?
math/bits.OnesCount64 只统计某一个 uint64 里有几个 1,它不操作位图本身,只是辅助函数。位图的核心是随机读写任意位置的 bit,不是数 1 的个数。
误用场景:想查“第 1000 位是否为 1”,却去调 OnesCount64(bits[15]) —— 这只能告诉你第 960~1023 位里总共几个 1,没法定位到第 1000 位。
- 真要用
math/bits,推荐bits.LeadingZeros64或bits.TrailingZeros64做稀疏位图的快速跳转 - 批量 set/clear 某段连续 bit 时,建议按
uint64对齐操作,避免单 bit 循环,性能差 10 倍以上 - 注意小端序不影响位图逻辑——Go 的位运算是平台无关的,
1 永远是最低位
并发写 BitMap 时为什么 panic 或结果错?
多个 goroutine 直接读写同一个 []uint64 元素会触发 data race,Go runtime 可能 panic,也可能静默出错(比如两个 goroutine 同时 set 第 123 位,最终只生效一次)。
不是所有场景都要加锁:如果写操作是“只增不删”且可容忍少量重复(如布隆过滤器),可用 sync/atomic 的 OrUint64;否则必须保护整个数组或分段加锁。
- 最简方案:用
sync.Mutex包一层Set/Get方法 - 高并发场景:按
uint64索引分段加锁,例如每 8 个uint64共用一个*sync.RWMutex - 千万别用
atomic.LoadUint64读单个uint64就以为线程安全——set 操作可能只改其中几位,非原子写会导致撕裂
用 github.com/zeebo/bloom 或 golang.org/x/exp/slices 能替代手写位图吗?
不能直接替代。前者是布隆过滤器封装,底层虽用位图但接口固定(只支持 add/test,不支持随机 get/set);后者压根没有位图相关功能——slices 是泛型切片工具集,和 bit 无关。
真正需要自定义行为时(比如带版本号的位图、持久化到 mmap 文件、支持 range 查询),还是得自己管内存布局和位运算逻辑。
- 如果只是去重+存在性判断,用
zeebo/bloom更省心 - 如果要支持
Count()、NextSet(1000)(找下一个为 1 的位置)、或和 protobuf 二进制互转,手写控制权更大 - 注意:
unsafe.Slice在 Go 1.21+ 可用于零拷贝构造大位图,但需确保底层数组对齐到 8 字节,否则uint64读写会 panic
事情说清了就结束
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











