範圍搜尋的六種實現方式
當你想用一些規則(例如時間範圍)來過濾查詢的時候,lucene給我們提供了許多方法實現。選擇越多意味著靈活性越大,但同時也意味著做出錯誤選擇的機會 也越大。下列程式碼封裝含了六種filter的使用方式和效能表現。並加入了選擇建議。
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.ConstantScoreQuery;
import org.apache.lucene.search.ConstantScoreRangeQuery;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.FilteredQuery;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryFilter;
import org.apache.lucene.search.RangeFilter;
import org.apache.lucene.search.RangeQuery;
import org.apache.lucene.search.TermQuery;
/**
* filter效能測試。
* 測試表明基於RangeQuery的查詢是較慢的實現方式。而基於filter的查詢則提供了較大靈活性,並且速度稍快。
*/
public class FilterPerformanceTests
{
private static IndexReader reader;
private static IndexSearcher searcher;
public static void main(String[] args) throws Exception
{
reader=IndexReader.open("/indexes/enron");
searcher=new IndexSearcher(reader);
///The query to run in all our tests
TermQuery tq=new TermQuery(new Term("contents","drink"));
//The filter criteria used by all tests
String filterField="date";
String lowerRange="20000101";
String upperRange="20001012";
int numQueriesPerTest=100;
timeQuery("Plain Term query", tq, null,numQueriesPerTest);
/*
* Method 1: BooleanQuery with mandatory TermQuery and RangeQuery
*
* 平均用時: 22.5 ms
* 是否變了搜尋結果得分: 是
* 注 解: 不推薦。
* 如果RangeQuery的搜尋範圍太大會導致拋出"too many clauses"的異常!
* 由於缺少filter term,它的效能表現不能讓人滿意。
* 但是這種方法卻被使用的很多,因為這種方法是唯一能完全用QueryParser
* 的文法來表示的。就像被沒有讀過lucene api的人使用一樣。
*/
RangeQuery rq1=new RangeQuery(new Term(filterField,lowerRange),
new Term(filterField,upperRange),true);
BooleanQuery bq1=new BooleanQuery();
bq1.add(new BooleanClause(tq,BooleanClause.Occur.MUST));
bq1.add(new BooleanClause(rq1,BooleanClause.Occur.MUST));
timeQuery("BooleanQuery with range", bq1, null,numQueriesPerTest);
/*
* Method 2: TermQuery with filter passed to searcher
*
* 平均用時: 4.53 ms
* 是否變了搜尋結果得分: 否
* 註解: 推薦。
*/
RangeFilter rf2=new RangeFilter(filterField,lowerRange,upperRange, true,true);
timeQuery("Query + rangefilter", tq, rf2,numQueriesPerTest);
/*
* Method 3: FilteredQuery using TermQuery and RangeFilter
*
* 平均用時: 4.38 ms
* 是否變了搜尋結果得分: 否
* 註解: 推薦。最快的選擇,並且可以最靈活的使用,因為可以把很多的filter封裝在一個Query中。
*/
RangeFilter rf3=new RangeFilter(filterField,lowerRange,upperRange, true,true);
FilteredQuery fq3=new FilteredQuery(tq,rf3);
timeQuery("FilteredQuery with rangefilter", fq3, null,numQueriesPerTest);
/*
* Method 4: BooleanQuery with mandatory TermQuery and ConstantScoreQuery(takes a filter)
*
* 平均用時: 4.85 ms
* 是否變了搜尋結果得分: 是
* 註解: 一個把filter表達成query的方式。(得分為常量)。
* 這並不是一個真正意義上的filter,因為這會被用作一個查詢來運行。
*/
RangeFilter rf4=new RangeFilter(filterField,lowerRange,upperRange, true,true);
ConstantScoreQuery csq4=new ConstantScoreQuery(rf4);
BooleanQuery bq4=new BooleanQuery();
bq4.add(new BooleanClause(tq,BooleanClause.Occur.MUST));
bq4.add(new BooleanClause(csq4,BooleanClause.Occur.MUST));
timeQuery("ConstantScoreQuery ", bq4, null,numQueriesPerTest);
/*
* Method 5: BooleanQuery with mandatory TermQuery and ConstantScoreRangeQuery
*
* 平均用時: 4.68 ms
* 是否變了搜尋結果得分: 是
* 註解: 一個較簡單的編碼方式。ConstantScoreQuery在內部為我們封裝了一個RangeFilter。
*/
ConstantScoreRangeQuery crq5=new ConstantScoreRangeQuery(filterField,lowerRange,upperRange,true,true);
BooleanQuery bq5=new BooleanQuery();
bq5.add(new BooleanClause(tq,BooleanClause.Occur.MUST));
bq5.add(new BooleanClause(crq5,BooleanClause.Occur.MUST));
timeQuery("ConstantScoreRangeQuery ", bq5, null,numQueriesPerTest);
/*
* Method 6: TermQuery with filter of QueryFilter wrapping a RangeQuery
*
* 平均用時: 0.94 ms
* 是否變了搜尋結果得分: 否
* 註解: 這 個查詢表現得更快,但是它和其他的查詢比較是不公平的。因為QueryFilter在內部有一個RangeQuery,
* 它緩衝了查詢結果,放在一個bitset中。在迴圈搜尋中,重用了這個bitset。
* 在更加模擬的測試或者在實際環境中,應用程式的設定和過濾的需求都是變化的,
* 所以這種緩衝是沒有意義的,不推薦使用。
* 而且內部RangeQuery的使用也會導致“Too many clauses”的異常。
*
* */
RangeQuery rq6=new RangeQuery(new Term(filterField,lowerRange),
new Term(filterField,upperRange),true);
QueryFilter qf6=new QueryFilter(rq6);
timeQuery("Query + QueryFilter wrapping a RangeQuery", tq, qf6,numQueriesPerTest);
searcher.close();
reader.close();
}
private static void timeQuery(String queryType,Query tq,
Filter filter, int numLoops) throws IOException
{
long start=System.currentTimeMillis();
int numDocs=0;
int top3Docs[]=new int[3];
float top3Scores[]=new float[3];
for(int l=0;l<numLoops;l++)
{
Hits h = searcher.search(tq,filter);
numDocs=h.length();
for(int i=0;i<Math.min(top3Docs.length,numDocs);i++)
{
h.doc(i).get("title");
if(l==0)
{
top3Docs[i]=h.id(i);
top3Scores[i]=h.score(i);
}
}
}
long end=System.currentTimeMillis();
float ave=((float)(end-start))/(float)numLoops;
System.out.println(queryType+ " took avg "+ave+" millis found. numDocs="+numDocs);
System.out.print("/t top docs[score]=");
for(int i=0;i<top3Docs.length;i++)
{
System.out.print(top3Docs[i]+"["+top3Scores[i]+"]/t");
}
System.out.println();
}
}
來源於http://hi.baidu.com/got_from_jlu/blog/item/54ccc8c7a0f862c039db49b0.html