
BitSet 适合什么场景的去重
BitSet 不是用来替代 HashSet 或 TreeSet 的通用去重工具,它只在整数范围明确、密集且非负时才真正“极速”。比如你有一亿个在 [0, 10000000) 内的 int,用 BitSet 只需约 1.2MB 内存,而 HashSet<integer></integer> 至少要 400MB+。但一旦数据含负数、稀疏(如全是偶数)、或最大值上亿,BitSet 就会浪费内存或直接不可用。
关键判断点:
- 数据是否全为非负整数
- 最大值 max 是否可接受(max / 8 / 1024 / 1024 MB 就是内存占用)
- 是否允许“查不到即不存在”,不关心原始顺序或频次
如何正确初始化并批量写入
别用 new BitSet() 默认构造——它初始容量极小,频繁扩容反而拖慢。直接按需指定位数:
int maxValue = 9999999; BitSet seen = new BitSet(maxValue + 1); // +1 防止 max 越界
写入时用 set(int index),不是 set(int index, boolean value) 的重载变体(后者易误设为 false)。批量导入推荐循环调用 set(),而非先建数组再 or ——JVM 对单次 set() 优化足够好:
- 避免把原始
int[]直接转成BitSet时漏掉负数检查,否则抛IndexOutOfBoundsException - 若数据来自文件流,边读边
set(x)比全读进内存再处理更省内存 -
set()是线程不安全的,多线程写入必须加锁或改用ConcurrentHashMap+ 分段 BitSet
查找是否存在比 contains() 更快?
不用封装一层 contains() 方法——它只是对 get(index) 的包装。直接用 seen.get(x),JVM 会内联且无额外开销。但要注意:
- 如果
x = seen.length(),get(x)返回false(不会抛异常),这容易掩盖越界 bug - 想确认是否“真在范围内存在”,得先
x >= 0 && x - 遍历所有已设位用
seen.nextSetBit(0)循环,比从 0 到length()全扫快得多(尤其稀疏时)
内存与性能的真实代价在哪
BitSet 的“极速”建立在两个隐性成本上:一是初始化时清零整个底层 long[] 数组(哪怕只用前几个 bit),二是 length() 返回的是逻辑长度(最高 set 位+1),但实际分配可能更大(按 64 位对齐)。例如存 1000 个数,最大值是 999,它仍会分配至少 ceil(1000/64)=16 个 long,即 128 字节——这点可以忽略;但若最大值是 2^28(268435456),就固定占 4MB,跟数据量无关。
真正容易被忽略的是:BitSet 无法序列化后跨平台可靠复用(不同 JVM 实现的内部布局可能不同),也不支持直接 mmap 到文件。如果需要持久化,要么手写二进制 dump(注意 byte order),要么转成 long[] 再保存——但恢复时仍得 new BitSet 并逐段 or。










