很多读者认为搜索完成后只要输出,整个过程就应该结束了。事实确实如此。不过输出的过程存在着以下几个问题
1. 以何种顺序输出?
2. 搜索结果是否全部输出?
3. 以什么形式输出?
下面,我们来一一解决。
4. 排序
4.1 自然排序
关于自然排序前面已经提到,是根据相关度进行排序。所谓相关度,就是前文提到过的文档得分。
注意:文档的得分在每次执行查找时都是不一样的,需要根据查找的关键字来确定。
4.1.1 explain
public Explanation explain(Query query, int doc) throws IOException
explaination类的toString()方法提供的信息能够将一个文档的得分情况详细地列举出来。这在调试中有着重要意义。
Hits htis = searcher.search(q);
for(int i=0; i<hits.length(); i++){
Document doc = hits.doc(i);
System.out.print(doc.get("bookname" + "\t\t"));
System.out.println(hits.score(i));
System.out.println(searcher.explain(q, hits.id(i)).toString());
}
文档得分主要由四部分内容决定:
1. tf:词条频率
2. idf:反转文档频率
3. boost:Field的激励因子
4. lengthNorm:长度因子
相关算法请参考其他相关资料
4.2 Sort排序
Sort是Lucene自带的一个排序工具,位于org.apache.lucene.search包中。
4.2.1 构造函数
public Sort();
public Sort(String field);
public Sort(String field, boolean reverse);
public Sort(String[] fields);
public Sort(SortField field);
public Sort(SortField[] fields);
其中boolean reverse表示指定升序还是降序排列。默认值为false降序,可设置为true升序。
下一个问题就是最后两种构造方法里的SortField是什么鬼
4.2.2 SortField
SortField是一个包装类型,使Sort类清楚地了解要进行排序的Field的各种信息
部分构造函数如下:
public SortField(String field)
public SortField(String field, boolean reverse)
public SortField(String field, int type)
public SortField(String field, int type, boolean reverse)
只解释一点,就是type,它表示Field的类型。SortField为此提供了几个常量,比如SortField.STRING,SortField.INT,SortField.FLOAT。如果构造函数中没有指定type时,SortField提供了SortField.AUTO由Lucene来对当前Field的类型进行判断。
在Sort内部,所有Field都会被转成SortField的对象数组进行保存。
4.2.3 三种排序方案
通过指定IndexSearch的search方法中的参数确定不同的排序方案。大体有三种:
1. 按文档得分进行排序
2. 按文档的内部ID好来排序
3. 按一个或多个Field来排序
4.2.3.1 按文档得分排序
Hits hits = searcher.searcher(q, Sort.RELEVANCE);
Hits hits = searcher.search(q, Sort.INDEXORDER);
4.2.3.3 按一个或多个Field来排序
在Sort类的内部有多个重载的setSort方法,它们会将用户提交的String型的Field信息转成SortField的对象,并存放于内部的数组中,或是直接将用户提交的SortField数组作为内部的数组,在排序时使用。
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
TermQuery q = new TermQuery(new Term("bookname", "女"));
//定义一个Sort对象
Sort sort = new Sort();
//定义一个SortField对象
SortField f = new SortField("bookNumber", SortField.INT, false);
//添加到Sort中
sort.setSort(f);
//查找并排序
Hits hits = searcher.search(q, sort);
for(int i=0; i<hits.length(); i++){
Document doc = hits.doc(i);
......
}
Sort sort = new Sort();
SortField sf1 = new SortField("publishdate", SortField.STRING, true);
SortField sf2 = new SortField("bookNumber", SortField.INT, false );
sort.setSort(new SortField[]{sf1, sf2});
5. 过滤
Lucene中所有的过滤器都来自于一个抽象的基类org.apache.lucene.search.Filter,它定义了过滤器的基本行为,其中声明的方法:
public abstract BitSet bits(IndexReader reader) throws IOException
读者读到这里怕是会想当然地认为BitSet必然是Lucene定义的一个类,然而事实是BitSet出自java.util,是一种位集合队列,每个元素只能取布尔值(详见Java SE API)。
Lucene以这两种取值来代表文档是否被过滤。
当Lucene返回结果时,会首先遍历BitSet,仅将那些对应值为true的文档返回。BitSet集合中,将其索引号看做是文档的内部ID号。
5.1 Filter的安全级别
Filter共设置了三种安全级别:
public static final int SECURITY_ADVANCED = 0;
public static final int SECURITY_MIDDLE = 1;
public static final int SECURITY_NORMAL = 2;
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
TermQuery q = new TermQuery(new Term("bookname", "女"));
//查找并排序
Hits hits = searcher.search(q, new AdvancedSecurityFilter());
for(int i=0; i<hits.length(); i++){
Document doc = hits.doc(i);
......
int level = Integer.parseInt(doc.get("securitylevel"));
switch(level){
case SECURITY_ADVANCED:
System.out.println("高级");
break;
case SECURITY_MIDDLE:
System.out.println("中级");
break;
case SECURITY_NORMAL:
System.out.println("一般");
break;
}
}
5.2 按范围过滤RangeFilter
顾名思义,不解释
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
TermQuery q = new TermQuery(new Term("bookname", "女"));
RangeFilter filter = new RangeFilter("publishdate", "2017-01-01", "2017-01-10", true, true);
//查找并排序
Hits hits = searcher.search(q, filter);
for(int i=0; i<hits.length(); i++){
Document doc = hits.doc(i);
......
int level = Integer.parseInt(doc.get("securitylevel"));
switch(level){
case SECURITY_ADVANCED:
System.out.println("高级");
break;
case SECURITY_MIDDLE:
System.out.println("中级");
break;
case SECURITY_NORMAL:
System.out.println("一般");
break;
}
}
5.3 在结果中查询QueryFilter
相信大家在很多搜索引擎中都看到过在结果中搜索这么一栏。什么意思呢?就是在一次检索完毕后希望从上一次检索的结果集合中进行二次检索。Lucene提供QueryFilter来帮助完成这项功能。
QueryFilter的使用只需要使其构造函数接收一个Query对象,该对象可以看做是前一次查询,只要在本次查询时将所构造的QueryFilter对象注入即可。
//进行第一次搜索
Term begin = new Term("publishdate", "2017-01-01");
Term end = new Term("publishdate", "2017-01-10");
RangeQuery q = new RangeQuery(begin, end, true);
//构建QueryFilter,将RangeQuery赋给它
QueryFilter filter = new QueryFilter(q);
//构造TermQuery,查询所有securitylevel为NORMAL的文档
Term normal = new Term("securitylevel", SECURITY_NORMAL + "");
TermQuery query = new TermQuery(normal);
IndexSearcher searcher = new IndexSearcher(INDEX_STORE_PATH);
//查找并排序
Hits hits = searcher.search(q, filter);
for(int i=0; i<hits.length(); i++){
Document doc = hits.doc(i);
......
int level = Integer.parseInt(doc.get("securitylevel"));
switch(level){
case SECURITY_ADVANCED:
System.out.println("高级");
break;
case SECURITY_MIDDLE:
System.out.println("中级");
break;
case SECURITY_NORMAL:
System.out.println("一般");
break;
}
}
在QueryFilter的bits方法中,调用IndexSearcher方法,对注入的Query进行一次检索,然后在要被返回的BitSet中,将检索结果所对应的文档标记为true,也就是说,过滤掉了所有不在检索范围内的文档。