重建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());