用 uint 做位掩码更安全,因 int 右移会符号扩展致结果错误;sync/atomic 位操作需用 oruint32/anduint32 或 cas;密集小整数集合宜用位数组而非 map[uint]struct{}。

Go 里用 uint 做位掩码比 int 更安全
Go 没有无符号移位的语法糖,但位运算底层依赖整数的二进制表示。用 int 做掩码时,右移(>>)可能触发符号扩展,尤其在高位为 1 时结果意外变负——这会让 & 判断失效。比如 int8(-1) >> 1 得到 -1,而不是你想要的 0x7F。
- 始终优先用
uint、uint32或uint64存储掩码值,避免符号干扰 - 如果必须从
int输入转换,显式转成uint:uint(v) & mask - 切忌直接对
int变量做多次>>后再与掩码比较,容易在边界值上出错
sync/atomic 对位字段的原子操作要绕开直接位运算
Go 的 sync/atomic 不提供 AndUint32 以外的位操作原子函数,也没有 OrUint64 或 XorUint32。想原子地置位/清位,不能靠 atomic.LoadUint32(&x) | mask 再 Store——中间存在竞态窗口。
- 用
atomic.OrUint32和atomic.AndUint32(Go 1.19+)完成基本置位/清位 - 需要翻转某一位?先读原值,用
^mask构造新掩码,再用atomic.CompareAndSwapUint32循环重试 - 别试图用
atomic.AddUint32(&x, mask)模拟或操作——除非mask是 2 的幂且确认无进位冲突
用 map[uint]struct{} 实现集合不如 []bool 或位数组紧凑
当元素 ID 是密集小整数(比如 0~1023),用 map[uint]struct{} 存集合,内存开销是位数组的 10 倍以上:每个 map bucket 至少 24 字节,还要哈希计算;而 1024 个 bool 只需 128 字节,位操作也更快。
- ID 范围可控(
)且稀疏度低时,用 <code>[]bool或自定义位数组结构体(含data []uint64和len) - 需要频繁遍历成员?位数组配合
bits.OnesCount64+bits.TrailingZeros64比 range map 快一个数量级 - 若 ID 稀疏(如用户 ID 是 64 位随机数),才考虑
map[uint64]struct{},但别用它做“是否包含”的高频判断——改用map[uint64]bool更直白
Go 标准库的 bits 包不是装饰品,该用就用
很多人手动写 v & (v-1) 清最低位,或循环移位数 1,其实 bits 提供了经过编译器特化、能生成 BMI 指令(如 POPCNT)的函数,性能和可读性都更好。
立即学习“go语言免费学习笔记(深入)”;
- 统计置位数:用
bits.OnesCount64(x),不是手写循环 - 找最低/最高置位索引:用
bits.TrailingZeros64(x)或bits.LeadingZeros64(x),注意返回的是前导零个数,需换算 - 反转比特序:用
bits.Reverse64(x),比查表或分治快得多 - 所有
bits函数对 0 输入都有定义,无需额外判空
位运算本身不难,难的是在并发、内存、边界值、编译器优化之间找平衡点。最常被跳过的一步是:没确认数据分布就选集合结构——结果 map 占满堆,GC 开始抖动。










