lucene3.0.3中的Spanquery和Spans介紹

      SpanQuery就是用來查詢不僅僅是含有term,並且存在的各個term的位置符合一定條件的doc,從源碼上講他最大的改變就是有了getSpans(IndexReader reader)方法,該方法返回的是Spans,在SpanQuery中使用Spans這個類來召回doc(在普通的termQuery中是使用termDocs進行召回),我們先看看Spans這個類。

      在Spans的javadoc中有這樣的說明:Spans用於枚舉term出現的位置,如果是在一個document中則枚舉在當前的doc中出現的各個位置,枚舉完了當前的doc的所有的位置,則會讀取下一個doc,這正是他的next()方法的意義,多次調用這個next方法可能會修改當前的docid(使用doc方法)也可能不會,因爲當前的term在當前的doc中可能出現了多次。Spans還有一個方法start()用於獲得當前的位置的開始位置(也就是在創建索引時的位置增量),還有end()方法,用於獲得當前的位置的結束位置。getPayload()方法,該方法用於獲得當前位置的payload,當然可能在建立索引的時候就沒有保存詞頻和位置信息,所以還有一個開關——isPayloadAvailable()方法,他表示當前的位置能否讀取payload。注意這裏返回的是Collection<byte[]>類型的payload,這是因爲可能有的Spans的子類(比如後面說的SpanNearQuery生成的Span,包含多個子Spans)會返回多個payload,所以用一個集合Collection,一個paylaod用byte[]表示。一句話總結下Spans:用於按照順序枚舉一個term在所有包含他的doc中的位置,可以讀取payload。

 

       我們看一個最簡單的SpanQuery——SpanTermQuery。

      SpanTermQuery其實本質上沒有任何用處,他的作用也是按照term進行查找doc(和TermQuery是一個意思),只要一個doc含有指定的term就會被召回,沒有任何其他的限制(和普通的termQuery的召回邏輯一樣),我覺得它存在的意義僅僅是讓我們接觸一下SpanQuery。他有了區別於普通的termQuery的地方——使用termPositions而不是termDocs進行查找(體現在使用Spans而沒有直接在query中使用Term),打分方式也發生了變化——計算tf與termQuery完全不一樣;第二個意義是在他的基礎上有一個PayLoadTermQuery,這個類可以使用payload進行打分。我們看一下SpanTermQuery的源碼。

      先看一下他最關鍵的方法:getSpans方法:

@Override
public Spans getSpans(final IndexReader reader) throws IOException {
	return new TermSpans(reader.termPositions(term), term);//返回的是一個TermSpans
}

 我們看一下TermSpans,尤其關注其next方法:

 

public TermSpans(TermPositions positions, Term term) throws IOException {
	this.positions = positions;
	this.term = term;
	doc = -1;
}

 在構造方法中會傳入一個termPositions,這個類是TermDocs的子類,他有一個額外功能是能讀取prx文件,也就是能得到每一個term在每一個doc中的出現的位置以及在該位置上的payload,當然他也能讀取倒排表,也就是能召回doc。我們看一下termSpan的next方法:

/**
 * 如果當前的term在當前的doc中的所有的出現位置讀取完了(count==freq)則繼續讀取下一個doc,然後讀取下一個位置,增加count++; 
 * 如果沒有讀取完,則僅僅讀取下一個位置。
 * */
@Override
public boolean next() throws IOException {當前的doc的所有的位置都讀取完了。
	if (count == freq) {//
		if (!positions.next()) {
			doc = Integer.MAX_VALUE;
			return false;
		}
		doc = positions.doc();
		freq = positions.freq();
		count = 0;
	}
	//讀取下一個位置,此時一定返回true,因爲當前的doc沒有讀取完。
	position = positions.nextPosition();
	count++;
	return true;
}

 這裏的count表示在當前的doc上所有的出現的次數(用freq表示)中已經讀取了多少次,如果已經讀取完了當前的doc的所有的次數,則讀取下一個doc,即進入if判斷,否則繼續讀取當前的doc。

 

SpanTermQuery的weight以及scorer

      SpanTermQuery並沒有改寫createWeight方法,他會生成一個SpanWeight,這個類沒有什麼特殊的,他會繼續生成一個SpanScorer用來回收doc和打分,重點看一下SpanScorer的nextDoc方法,因爲這個方法用於回收doc,這個方法會調用setFreqCurrentDoc方法,

/**
 * 設置當前doc的freq,他是通過讀取當前doc的多個位置來實現的。
 */
protected boolean setFreqCurrentDoc() throws IOException {
	if (!more) {
	        return false;
	}
	doc = spans.doc();//通過Spans獲得當前的doc(當然spans是根據其封裝的termPosition來實現的這個功能)
	freq = 0.0f;
	do {
		int matchLength = spans.end() - spans.start();
		freq += getSimilarity().sloppyFreq(matchLength);
		more = spans.next();
	} while (more && (doc == spans.doc()));//直到讀取完當前doc上所有的position,所以儘管spans的next方法只會讀取一個位置,但是在scorer裏面試讀取了一個完整的doc,並且每個位置都讀取了,
	return true;
}	

 scorer利用spans了讀取了一個完整的doc,並且根據出現的次數得出了freq的值,在score方法中就會使用這個值對當前的doc進行打分。

/**得分唯一不同的是計算tf發生了變化*/
@Override
public float score() throws IOException {
	float raw = getSimilarity().tf(freq) * value; // raw score
	return norms == null ? raw : raw * Similarity.decodeNorm(norms[doc]); // normalize
}

 

這樣SpanTermQuery就完了,他也是根據term進行召回doc,只不過他是將倒排表(termDocs,在SpanQuery中是使用了TermPositions)封裝在了Spans裏面,使用spans來一個一個位置的讀取,並且得分的計算方式發生了變化。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

     

      

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章