位图算法专用于整数去重与存在性判定,核心优势是省空间和准判断;需满足数据为整数、值域集中、可控映射三个前提,且应避免std::vector,优选bitset、vector或原子类型,负数需归一化,超大值域宜分桶,遍历结果需优化策略。

位图算法不是万能的加速器,它专为整数去重与存在性判定而生,核心价值在“省空间”和“准判断”,而不是盲目替换哈希表。能否真正优化性能,取决于你是否严格满足三个前提:数据是整数、值域相对集中、且你愿意为这个前提做映射和边界控制。
选对底层容器:别用 std::vector
它看着省空间,实则埋雷:返回代理对象,不能取地址,不支持原子操作,编译器难内联,多线程下 set() 可能丢位。性能比手写位操作慢 2–3 倍,还无法用 AVX 加速。
推荐方案:
- 值域固定且编译期可知(比如 HTTP 状态码 0~999):直接用 std::bitset
,零开销、带边界检查、接口干净 - 值域运行时确定(比如读取日志后算出 max_id = 87654321):用 std::vector
,每个元素管 64 个 bit,预分配大小为 (max_val + 63) >> 6 - 需要线程安全写入:用 std::vector<:atomic>>,set() 用
fetch_or(1ULL
正确映射输入:负数、偏移、越界都得处理
位图只认非负整数索引,“值即索引”只是特例,不是默认行为。原始数据若含负数或有固定偏移,必须显式归一化。
常见做法:
- 数据范围已知为 [100000, 10999999]:插入前先
x -= 100000,位图按size = 10900000构造 - 含负数:拆成两个位图——
pos_bitmap存 ≥0 的数,neg_bitmap存-x(如查 -5 就 test neg_bitmap[5]) - 输入可能越界(如 x ≥ size):每次 set() 和 get() 前必须加 if (i >= size_) return;,否则静默崩溃或读垃圾值
应对超大值域:分桶比硬扛更实用
若原始数据是 int32_t 全范围(-2³¹~2³¹−1),建单一位图要 512MB+,但实际只出现百万个不同值——这就是典型稀疏场景,硬上位图只会浪费 99.9% 内存。
更可行的策略:
- 按高 K 位哈希分桶:例如
bucket_id = x >> 24,共 256 个桶,每个桶用独立std::vector<uint8_t></uint8_t>管理其低 24 位(0~16777215) - 桶内偏移 =
x & 0xFFFFFF,内存按需分配,避免全局最大值主导开销 - 成员判定需查所有相关桶(如查 x,先算 bucket_id,再查对应桶内偏移),但总内存可控、可并行构建
遍历去重结果:别扫全量值域
位图本身不存数值,只存状态。想拿到所有不重复的数,本质是扫描整个位空间。如果值域是 0~10⁹,哪怕只设了 100 个 bit,for (i=0; i 仍是 O(10⁹)。
提速关键:
- 若只需统计数量:用
__builtin_popcountll()批量统计每个 uint64_t 中的 1 的个数 - 若需高频遍历结果:额外维护一个
std::vector<uint32_t></uint32_t>记录首次 set 的值,或改用 Roaring Bitmap 这类带索引结构 - 若数据局部密集(如连续 ID 段):用 AVX2 跳过全零块——
_mm256_testc_si256(v, _mm256_setzero_si256())判断 32 字节是否全零,跳过即可


















