重建Query对象是个递归的过程,是个广度遍历树的过程。
BooleanQuery.reWrite()函数:
BooleanQuery clone = null; // recursively rewrite
for (int i = 0 ; i < clauses.size(); i++) {
BooleanClause c = clauses.get(i);
Query query = c.getQuery().rewrite(reader);//重写
if (query != c.getQuery()) { // clause rewrote: must clone/分支重写过了,那么必须克隆一份
if (clone == null)
clone = (BooleanQuery)this.clone();
clone.clauses.set(i, new BooleanClause(query, c.getOccur()));
}
}
if (clone != null) {
return clone; // some clauses rewrote
} else
return this; // no clauses rewrote
为什么要重写Query呢?
因为lucene不能直接处理MultiTermQuery,比如PrefixQuery、FuzzyQuery, TermQuery的重写直接返回其本身。
对MultiTermQuery可以有两种处理方法:
方法一,将多个term组成一个term,将包含他们的文档号取出来放到一起,作为一个倒排表来参与倒排表的合并。
方法二,将多个term每个搞成一个TermQuery,它们一起组成一个BooleanQuery,他们之间是OR关系。
1、 ConstantScoreQuery采用的是第一种方法,其构造
new ConstantScoreQuery(new MultiTermQueryWrapperFilter<MultiTermQuery>(query))
其中MultiTermQueryWrapperFilter的函数getDocIdSet() 是用于将多个term的倒排表进行合并的。
其函数的主体如下:
final FixedBitSet bitSet = new FixedBitSet(reader.maxDoc());//标志位的一个bitSet
final int[] docs = new int[32];//用于取docId和freqs的缓冲
final int[] freqs = new int[32];//用于取docId和freqs的缓冲
TermDocs termDocs = reader.termDocs();//得到倒排表的读取器
try {
int termCount = 0;
do {
Term term = enumerator.term();
if (term == null)
break;
termCount++;
termDocs.seek(term);//得到该term的倒排表
while (true) {
final int count = termDocs.read(docs, freqs);//从倒排表中读取出文档号和词频
if (count != 0) {
for(int i=0;i<count;i++) {
bitSet.set(docs[i]);//对该文档号在bieSet上对应的位置设置为1
}
} else {
break;
}
}
} while (enumerator.next());
这样的算法使用在lucene中很多地方都用到了,一是使用缓存,二是使用bit标志位
2、ScoringBooleanQueryRewrite以及子类ConstantScoreBooleanQueryRewrite采用方法二
其rewrite函数主体:
do {
Term t = enumerator.term();
if (t != null) {
TermQuery tq = new TermQuery(t); // found a match
tq.setBoost(query.getBoost() * enumerator.difference()); // set the boost
result.add(tq, BooleanClause.Occur.SHOULD); // add to query
count++;
}
} while (enumerator.next());