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,滿足查詢要求