如何在 Java 中利用 BitSet.cardinality() 统计位图中设置为 true 的总位数
Java中BitSet的cardinality()方法可直接统计位图中设为true的位数,采用稀疏位计数优化,时间复杂度接近O(1)。空BitSet返回0,多线程需手动加锁。与手动遍历相比性能提升显著,尤其适用于稀疏位图。注意区分length()与cardinality(),前者是逻辑长度而非真值个数。
在 Ja va 里,如果你想统计一个 BitSet 中到底有多少位被设为 true,cardinality() 就是最简单直接、效率也最高的那个方法。它不需要你写循环去遍历每一位,也不用引入什么第三方库,一行代码就能搞定。
cardinality() 的行为和边界条件
这个方法内部做的是稀疏位计数优化——说白了就是分段查表加上 Long.bitCount。从时间复杂度上说,它接近 O(1),严格来讲是 O(有效字长数),但在绝大多数场景下完全可以当作常量来看。使用时有几个点需要留意:
- 空
BitSet(没调用过任何set)返回0,这很符合直觉。 - 哪怕你设置了索引 1,000,000 这一位,中间全是
false,cardinality()也只统计真正置位的那些位,不会傻乎乎地把整个范围扫一遍。 - 调用前不用纠结要不要先调
trimToSize(),因为方法本身已经过滤掉了那些没被分配的 word 段。 - 注意线程安全——多线程并发的场景下,如果你不手动加锁,结果可能会对不上。
与手动遍历的性能对比
有的人可能会想着自己写个循环,用 nextSetBit() 或 get(i) 逐位统计。这么做不仅看着啰嗦,跑起来也慢得多。举个例子:
// ❌ 不推荐:O(n) 全量扫描,n 是最大索引+1int count = 0;for (int i = 0; i < bs.length(); i++) { if (bs.get(i)) count++;}// ✅ 推荐:O(实际置位块数),快一个数量级以上int count = bs.cardinality();尤其是当 BitSet 很稀疏的时候——比如只在索引 10000 和 200000 两处置了 true——cardinality() 几乎是瞬间出结果,而手动循环要老老实实检查 20 万次,差距不是一个量级。
常见误用:混淆 length() 和 cardinality()
BitSet.length() 返回的是「最高置位索引 + 1」,根本不是 true 的个数;它甚至可能大于实际内部容量(因为数组没压缩)。不少初学者会犯这样的错误:
- 误以为
bs.length() == bs.cardinality()—— 实际上前者是“逻辑长度”,后者才是“真值个数”,两码事。 - 拿
bs.size()(返回内部 long[] 数组的总容量)来估算 true 位数——这完全是两回事,返回值通常远大于实际置位数。 - 在从来没有 set 任何位的时候,
bs.length()返回 0,cardinality()也返回 0,此时两者数值相等,但这个巧合不能推广到有数据的情况。
真正需要记住的就是:统计 true 位数,只用 cardinality()。它不撒谎,不近似,JVM 对它做了深度优化。唯一需要操心的就是确保操作的是同一个 BitSet 实例,并且在并发写入时别漏掉同步。


































