转载自:http://blog.csdn.net/bestdowt1314/article/details/5837545
范围搜索的六种实现方式
当你想用一些规则(例如时间范围)来过滤查询的时候,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();
}
}
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();
}
}