BitSet通过1 bit表示1个非负整数的存在状态实现极致压缩,空间利用率近理论极限,但仅适用于值域可控、分布稠密的整数去重场景。

BitSet 是一种用二进制位(bit)表示布尔状态的紧凑数据结构,它在海量数据去重场景中能实现远超常规集合(如 HashSet)的空间压缩,尤其适合整数范围明确、分布密集的场景。
为什么 BitSet 能极致压缩?
核心在于“一个 bit 表示一个元素是否存在”:
- 存储整数 n,仅需设置第 n 位为 1(索引从 0 开始);
- 不存值本身,不存哈希、指针、对象头等额外开销;
- Java 中 BitSet 底层用 long 数组实现,64 位/long,空间利用率接近理论极限(1 bit/元素);
- 对比 HashSet
适用前提:必须满足三个关键约束
BitSet 去重不是万能方案,只有在以下条件同时成立时才发挥极致优势: - 数据类型为非负整数(或可无损映射为非负整数,如字符串经一致性哈希 + 取模后落入合理范围); - 值域范围可控且不过大(例如 0~1 亿,对应约 12.5 MB 内存;若到 100 亿,需 ~1.25 GB,需权衡); - 存在大量重复,且整体分布相对稠密(稀疏场景下,大量连续 0 位仍占空间,压缩收益下降)。
实战技巧:绕过限制、提升鲁棒性
面对现实数据的复杂性,可通过组合策略拓展 BitSet 边界:
- 偏移+缩放处理负数与浮点:如所有温度值 ∈ [−50, 50],可统一加 50 映射为 [0, 100];浮点转整数需注意精度损失,建议先离散化(如保留一位小数 → ×10 取整);
- 分桶 BitSet 管理超大值域:将 0~1010 拆为 1000 个桶(每桶 107),用 ConcurrentHashMap
警惕:不是所有“海量”都适合 BitSet
若原始数据是 UUID 字符串、长文本、任意长整数或高度稀疏(如只出现 100 个离散大整数),强行转 BitSet 会导致: - 映射后值域爆炸(如 UUID → long 可能碰撞,转字符串哈希又易冲突); - 分配巨大但几乎全零的位数组,内存浪费严重; - 初始化耗时长、GC 压力大。此时应优先考虑 RoaringBitmap(对稀疏/分段数据自动优化)、Redis HyperLogLog(近似去重)或 LSM-tree 类引擎。

















