BitSet是Java中用于处理位集合的高效工具类,其内部以long数组形式存储二进制位。length方法作为BitSet的常用API,经常被开发者用来获取位集的“长度”,但很多人对其返回值的准确定义存在误解。要正确使用这个方法,必须先弄清楚它到底度量了什么。

BitSet.length的官方定义
根据Java官方文档,BitSet.length()返回的是“此BitSet的逻辑大小”,具体定义为:最高置位索引加一。换句话说,如果位集中第n位(从0开始计数)是最后一个被设置为true的位,那么length返回n+1;如果位集中没有任何位被设置,则返回0。它并不代表位集中实际为1的位的总数,也不代表底层分配的物理存储空间大小。
这个定义意味着length只关心“最高有效位”的位置,而不关心中间有多少位是0或者有多少位是1。例如,当我们只设置了索引为64的位,那么length就是65,即使前面的0到63全是0。这种度量方式反映了BitSet作为一个可变长位向量的“有效使用长度”概念,而不是元素个数。
与size和cardinality的区别
为了理解length的准确含义,必须把它和另外两个方法区分开。size()返回的是BitSet底层数组当前分配的比特容量,通常是64的整数倍,且大于等于length;cardinality()返回的是位集中实际被设置为true的位的数量。三者的语义完全不同,下面用表格说明:
| 方法 | 含义 | 示例(仅置位第64位) |
|---|---|---|
| length() | 最高置位索引+1 | 65 |
| size() | 底层分配比特数 | 128(默认扩容后) |
| cardinality() | 置位总数 | 1 |
从表中可以看出,如果我们错误地把length当成元素数量,在统计或遍历时就会出问题。比如用length做循环上限去数1的个数,虽然能覆盖最高位,但效率远低于cardinality;而如果以为length是物理大小,又可能误判内存占用。
源码层面的计算逻辑
在OpenJDK的实现中,length方法会从最后一个非零的long单元开始,利用Long.numberOfLeadingZeros计算出最高位的位置,再换算为全局索引。简化逻辑如下:
// 简化版BitSet.length核心思路
public int length() {
int wordIndex = words.length - 1;
while (wordIndex >= 0 && words[wordIndex] == 0) {
wordIndex--;
}
if (wordIndex < 0) {
return 0;
}
// 在wordIndex这个long里找最高非0位
long w = words[wordIndex];
// numberOfLeadingZeros返回高位0的个数,64减掉就是最高位在long内的位置
int highestBitInWord = 63 - Long.numberOfLeadingZeros(w);
return (wordIndex << 6) + highestBitInWord + 1;
}
上述代码清晰展示了length只依赖“最后一个非零long以及其中的最高1位”,完全不涉及对全部位的计数。这也是为什么它的时间复杂度是O(1)到O(n/64),而cardinality必须遍历所有long去计数。
实际应用中的注意事项
在使用BitSet做序列化或网络传输时,有些人会用length来决定需要写出多少个比特。这时要注意,length只保证覆盖所有置位,但如果接收方按length重建BitSet,中间未置位的位会被默认为0,这通常没问题;可如果你用length来预估“有效数据量”,就不如cardinality直观。
另一个常见错误是在稀疏位集上用length做循环上限来执行某些操作。例如位集只在第1000位和第1位置位,length是1001,循环1001次但只两处有效,浪费严重。此时应配合nextSetBit方法做跳跃遍历:
BitSet bs = new BitSet();
bs.set(1);
bs.set(1000);
// 正确遍历置位
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
System.out.println("置位索引: " + i);
}
// 错误示范:用length遍历
for (int i = 0; i < bs.length(); i++) {
if (bs.get(i)) {
System.out.println("置位索引: " + i);
}
}
从示例可以看出,基于nextSetBit的写法跳过了大量0位,而依赖length的写法虽然结果正确,但执行了1001次循环判断。在性能敏感场景中,这种差异会被放大。
如何选择正确的度量方法
总结来说,当你需要“位集占用的逻辑区间长度”时,用length;需要“分配容量”时,用size;需要“真实元素数”时,用cardinality。在代码评审中,若看到有人用length统计个数,应当指出这是概念混淆。只有厘清这三者的边界,才能写出准确且高效的位运算代码。
最后提醒,BitSet在多线程环境下非线程安全,如果多个线程同时修改并读取length,需自行加锁或使用并发容器。但无论并发与否,length的语义始终固定为最高置位加一,不会因使用场景变化而改变。