實現得分的PrefixQuery

(先聲明一下,我使用的lucene的版本是lucene4.7.2)

在lucene中,有一種類型的query叫做MultiTermQuery,故名思議,他是要涉及到很多個term的query,比如我們常用的WildcardQuery、FuzzyQuery、PrefixQuery、TermRangeQuery、NumericRangeQuery等,他們都是需要按照一個或者多個term按照一定的邏輯找到多個term,然後再重寫由找到的這些term形成的TermQuery進入一個新的Query(比如BooleanQuery、或者ConstantScoreQuery),但是有個一指的注意的地方是:有些MultiTermQuery是不得分的,也就是在返回的時候不會按照得分排序,比如PrefixQuery,的不得分是由每個MultiTermQuery使用的rewriteMethod指定,也就是由重寫規則指定。本文的目的不在於討論重寫規則,而是想實現一個可以得分的PrefixQuery(業務場景是我們要使用PrefixQuery做搜索框中提示詞的排序,所以必須實現得分)。

 

實現原理很簡單,在指定重寫規則的時候將重寫規則指定爲得分的規則(當然這裏涉及到重寫規則的實現,這裏本文不討論),在org.apache.lucene.search.MultiTermQuery類中含有SCORING_BOOLEAN_QUERY_REWRITE這個重寫規則從他的名字中就可以理解是封裝爲一個BooleanQuery,並且計算分數。他的邏輯很簡單,將搜索到的多個termQuery封裝成一個booleanQuery,每一個termQuery都是optional的,也就是對多個termQuery取並集。但是Booleanquery有個需要注意的地方,他不能有太多的clause,不然會報錯,默認是1024個,所以我們需要修改這個值,做到這裏就算是完成了。我的代碼如下:

/**
 * 由於solr自帶的PrefixQuery是不得分的,不能滿足提示詞的排序要求,所以重寫這個query.
 */
public class ScoredPrefixQuery extends PrefixQuery {

	//從詞典表中得到的term的限制,用於做測試的,實際中不用
	private int limit = -1; 
	
	static{
		BooleanQuery.setMaxClauseCount(Integer.MAX_VALUE);//設置BooleanQuery的最多的子query的個數爲Integer.MAX_VALUE。
	}
	
	public ScoredPrefixQuery(Term prefix) {
		super(prefix);
		//重置重寫規則,使用得分的booleanQuery,此處存在的問題是可能會發生BooleanQuery.TooManyClauses,所以要在得到term的時候需要做限制
		setRewriteMethod(org.apache.lucene.search.MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
	}
	/**
         * 從詞典表中得到前綴匹配的term的方法
         */
	@Override
	public TermsEnum getTermsEnum(Terms terms, AttributeSource atts) throws IOException {
		 TermsEnum tenum = terms.iterator(null);
		 if (getPrefix().bytes().length == 0) {
		      // no prefix -- match all terms for this field:
		     return tenum;
		 }
		 return new PrefixTermsEnum(tenum, getPrefix().bytes()) {
			 int already = 0;
			 final int termLimit =  limit==-1?BooleanQuery.getMaxClauseCount():limit;//設置limit只是用於做測試的。
			 
			 @Override
			public BytesRef next() throws IOException {
				
				BytesRef ref = super.next();//先調用父類方法,即從詞典表中讀取,
				if(ref == null){//如果真的讀完了,就返回null。
					return null;
				}else{//沒有讀取完,則判斷是否已經讀取了太多的term
					//最多的BooleanClause的個數
					if(already++ < termLimit){//一個前綴最多從詞典表中得到booleanquery的MaxClause個,這樣就不會報錯了。
						return ref;
					}
					return null;
				}
			}
		 };
	}
	
	public int getLimit() {
		return limit;
	}
	
	//做測試用的
	public void setLimit(int limit) {
		this.limit = limit;
	}
	
//這個測試的前提是我們在索引中僅僅保存了只有id域的100個document,id爲從0-99,省略了建立索引的代碼。
	public static void main(String[] args) throws IOException {
		
		IndexReader reader = DirectoryReader.open(getDirectory());
		
		IndexSearcher search = new IndexSearcher(reader);
		ScoredPrefixQuery q = new ScoredPrefixQuery(new Term("id","1"));//這一行和下面的PrefixQuery q 這一行是區分的,如果使用這一行則只會搜到3個,並且得分不是1.0f,也就是是得分的。
		q.setLimit(3);//設置最多爲3個。
		
//		PrefixQuery q = new PrefixQuery(new Term("id", "1"));//如果使用lucene中默認使用的PrefixQuery則會搜到11個,並且得分都是1.0f,也就是沒有得分。
		
		TopDocs td = search.search(q, 100);
		for(ScoreDoc sd:td.scoreDocs){
			System.out.println(sd.score);
		}
		System.out.println(td.scoreDocs.length);
		
	}	
}

 

這樣就完成了得分的前綴匹配的query,如果要在solr中使用,還需要自己定義queryparser的插件,這個留在以後再寫博客。

 

 

 

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