多个SHOULD的倒排表合并

BooleanScorer中的一些变量跟方法

static final int SHIFT = 11;
static final int SIZE = 1 << SHIFT;
static final int SET_SIZE = 1 << (SHIFT - 6);
static class Bucket {
   // 谋篇文档的分数
   double score;
   // 某篇文档包含查询关键字的个数(去重)
   int freq;
}
// 初始化一个SIZE大小的数组, 数组下标是文档号。
final Bucket[] buckets = new Bucket[SIZE];
// 每一个数组元素的二进制值的每一个bit位用来描述文档号是否出现(后面会解释这句话)
final long[] matching = new long[SET_SIZE];

每一个term匹配到的每一个文档号都会作为参数传入到下面的collect(int doc)方法中,用来统计文档号出现的次数

public void collect(int doc) throws IOException {
     //doc为文档号, MASK的值为2047,作为理解倒排表合并的原理,我们不用考虑文档号大于2047的情况
     final int i = doc & MASK;
     // 每32篇文档都会记录在matching[idx]数组的同一个元素中
     // 比如说 0~31的文档号就被记录在 matching[0]这个数组元素中(看不懂?没关系,往下看就知道了)
     final int idx = i >>> 6;
     // 用来去重的存储文档号, 二进制表示的数值中,每个为1的bit位的所属第几位就是文档号的值
     // 比如 00...01001(32位二进制), 说明存储了 文档号 0跟3
     // matching在后面遍历中使用,因为我们还要判断每一篇文档出现的次数是否满足minSHouldMatch
     // 那么通过这个matching值就可以从buckets[]数组中以O(1)的复杂度找到每一篇文档出现的次数
     matching[idx] |= 1L << i;
     // 引用bucket对象,buckets[]数组下标是文档号
     // bucket中的freq统计某个文档号出现的次数
     final Bucket bucket = buckets[i];
     bucket.freq++;
     // 这里可以看出,打分是一个累加的过程
     bucket.score += scorer.score();
  }

在上面的collect(int doc)方法调用结束后,每篇文档的包含查询关键字的个数(去重)都已经计算完毕,接着只需要一一取出跟minShouldMatch的值进行比较,大于minShouldMatch的文档号即是满足要求长度文档。 前面说道,Bucket[]数组的下标是文档号,那么我们通过matching[idx],就能以O(1)的时间复杂度知道Bucket[]数组中哪些下标是有值的。

 private void scoreMatches(LeafCollector collector, int base) throws IOException {
   // 取出前面计算出的数组
   long matching[] = this.matching;
   for (int idx = 0; idx < matching.length; idx++) {
     // 取出其中一个matching[]数组的值
     long bits = matching[idx];
     // 反序列化的过程,得到所有的文档号
     while (bits != 0L) {
       int ntz = Long.numberOfTrailingZeros(bits);
       int doc = idx << 6 | ntz;
       // 每得到一个文档号就调用下面的方法去跟minShouldMatch比较
       scoreDocument(collector, base, doc);
       bits ^= 1L << ntz;
    }
  }
}
 
   private void scoreDocument(LeafCollector collector, int base, int i) throws IOException {
   final FakeScorer fakeScorer = this.fakeScorer;
   final Bucket bucket = buckets[i];
   // if语句为true:文档号出现的次数满足minShouldMatch
   if (bucket.freq >= minShouldMatch) {
    ... ...
     // 根据之前得到的windowBase值,恢复文档号真正的值
     final int doc = base | i;
    ... ...
     // 满足minShouldMatch的文档号传给Collector,完成结果的收集
     collector.collect(doc);
  }
   bucket.freq = 0;
   bucket.score = 0;
}

例子

  文档0:a
  文档1:b
  文档2:c
  文档3:a c e
  文档4:h
  文档5:c e
  文档6:c a
  文档7:f
  文档8:b c d e c e
  文档9:a c e a b c

查询关键字如下

BooleanQuery.Builder query = new BooleanQuery.Builder();
query.add(new TermQuery(new Term("content", "a")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "b")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "c")), BooleanClause.Occur.SHOULD);
query.add(new TermQuery(new Term("content", "e")), BooleanClause.Occur.SHOULD);
query.setMinimumNumberShouldMatch(2);

处理包含关键字“a”的文档

将包含“a”的文档号记录到bucket[]数组中(图中书写错误。。。谅解~应该是的bucket[])

 

 

处理包含关键字“b”的文档

将包含“b”的文档号记录到Bucket[]数组中

 

 

处理包含关键字“c”的文档

将包含“c”的文档号记录到Bucket[]数组中

 

 

处理包含关键字“e”的文档

将包含“e”的文档号记录到Bucket[]数组中

 

统计文档号

接着我们根据matching[]数组中的元素以 0(1) 的时间复杂度找到Bucket[]中所有的文档号,如果不通过matching[],那么我们必须对Bucket[]的每一个元素遍历查找

在当前的例子中,我们只要用到matching[]的第一个元素,第一个元素的值是879(为什么只要用到第一个元素跟第一个元素的是怎么得来的,在BooleanScorer类中我加了详细的注释,这里省略)

 

根据二进制中bit位的值为1,这个bit位的位置来记录包含查询关键字的文档号,包含查询关键字的文档号只有0,1,2,3,5,6,8,9一共8篇文档,接着根据这些文档号,把他们作为bucket[]数组的下标,去找到每一个数组元素中的值,如果元素的值大于等于minShouldMatch,对应的文档就是我们最终的结果,我们的例子中

query.setMinimumNumberShouldMatch(2);

所以根据最终的bucket[]

 

只有文档号3,文档号5,文档6,文档8,文档9对应元素值大于minShouldMatch,满足查询要求

posted @ 2019-03-29 17:46  車輪の唄  阅读(14)  评论(0编辑  收藏  举报  来源