一句话:
`BitSetIterator` 就是把 java.util.BitSet 包装成 DocIdSetIterator 的“游标”,让你可以像扫普通倒排表一样,按 docID 从小到大遍历所有被置 1 的位,而不用自己手工 `nextSetBit(...)`。
---
1. 背景
Lucene 的 `DocIdSetIterator` 抽象只有两个核心动作:
- `nextDoc()` – 返回下一个存在的 docID
- `advance(target)` – 跳到 ≥ target 的第一个存在的 docID
任何“文档号集合”想参与查询/过滤,都必须提供这个接口。
`BitSet` 本身只有 `get()/set()/nextSetBit(int)`,没有标准的迭代器语义,于是就有了 `BitSetIterator` 做适配。
---
2. 内部实现(8.5.0 代码)
```java
final class BitSetIterator extends DocIdSetIterator {
private final BitSet bits;
private final int length; // 最大 docID + 1
private int doc = -1; // 当前 docID
BitSetIterator(BitSet bits, int length) {
this.bits = bits;
this.length = length;
}
@Override
public int docID() { return doc; }
@Override
public int nextDoc() throws IOException {
return advance(doc + 1);
}
@Override
public int advance(int target) throws IOException {
if (target >= length) {
return doc = NO_MORE_DOCS;
}
return doc = bits.nextSetBit(target);
}
@Override
public long cost() {
return bits.cardinality(); // 估计还剩多少文档
}
}
```
- 完全委托给 `BitSet.nextSetBit(...)`,O(1) 起步,最坏 O(n),但对稀疏集合仍很快。
- `cost()` 直接返回 `cardinality()`,方便上层 `ConjunctionDISI/DisjunctionDISI` 做排序、选 lead。
---
3. 典型出场场景
1. 缓存过滤器
`CachingWrapperFilter` 把第一次计算出来的 `DocIdSet` 做成 `BitSet`,以后每次搜索直接 `BitSetIterator` 复现,避免重新倒排解压。
2. 多值字段去重
某些自定义 `FieldCache` 或 `DocValues` 去重逻辑,会先把出现过的 docID 记到 `BitSet`,再用 `BitSetIterator` 一次性吐出。
3. 测试/调试
单元测试里想快速构造一个“已知文档号集合”的 `Scorer`,直接 `new BitSetIterator(bitSet, maxDoc)` 即可。
---
4. 优劣速览
优点
- 实现极简,无额外内存(除了 BitSet 本身)。
- `advance` 是位级操作,对稀疏集非常高效。
缺点
- `BitSet` 用 `long[]` 存位图,对超大索引(>2B doc)会爆内存;Lucene 9 以后已逐步用 `RoaringDocIdSet` 替代。
- 只能正向扫描,不支持倒序;如果业务需要 `previousDoc()` 得自己再包一层。
---
一句话记忆
`BitSetIterator` = “把内存里的 0/1 位图变成 Lucene 能用的倒排游标”,专用于缓存、小集合、快速重建场景,是过滤器结果重用的最廉价方案。
531

被折叠的 条评论
为什么被折叠?



