Lucene的多域查询、结果中查询、查询结果分页、高亮查询结果和结果评分

1.针对多个域的一次性查询

1.1.三种方案

使用lucene构造搜索引擎的时候,如果要针对多个域进行一次性查询,一般来说有三种方法:

第一种实现方法是创建多值的全包含域的文本进行索引,这个方案最简单。但是这个防范有个缺点:你不能直接对每个域的加权进行控制。

第二种方法是使用MultiFieldQueryParser,它是QueryParser的子类,它会在后台程序中实例化一个QueryParser对 象,用来针对每个域进行查询表达式的解析,然后使用BooleanQuery将查询结果合并起来。当程序向BooleanQuery添加查询子句时,默认 操作符OR被用于最简单的解析方法中。为了实现更好的控制,布尔操作符可以使用BooleanClause的常量指定给每个域。如果需要指定的话可以使用 BooleanClause.Occur.MUST,如果禁止指定可以使用BooleanClause.Occur.MUST_NOT,或者普通情况为 BooleanClause.Occur.SHOULD。下面的程序展示的是如何创建MultiFieldQueryParser类的方法:

  1. // 在这四个域中检索
  2. String[] fields = { "phoneType", "name", "category", "price" };
  3. Query query = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);

第三种方法就是使用高级DisjunctionMaxQuery类,它会封装一个或者多个任意的查询,将匹配的文档进行OR操作。

1.2.方案选择

以上三种方案中,并不是第三种方案最好,也不是第一种方案就最差。哪种实现方式更适合你的应用程序呢?答案是“看情况”,因为这里存在一些取舍。全包含域 是一个简单的解决方案——但这个方案只能对搜索结果进行简单的排序并且可能浪费磁盘空间(程序可能对同样的文本索引两次),但这个方案可能会获得最好的搜 索性能。

MultiFieldQueryParser生成的BooleanQuery会计算所有查询所匹配的文档评分的总和 (DisjunctionMaxQuery则只选取最大评分),然后它能够实现针对每个域的加权。你必须对以上3中解决方案都进行测试,同时需要一起考虑 搜索性能和搜索相关性,然后再找出最佳方案。

2.在结果中查询

2.1.两种方案

在检索结果中再次进行检索,是一个很常见的需求,一般有两种方案可以选择:

①使用QueryFilter把第一个查询当作一个过滤器处理;

②用BooleanQuery把前后两个查询结合起来,并且使用BooleanClause.Occur.MUST。

针对第一种方法,我需要解释一下。QueryFilter在Lucene的2.x版本中是存在的,但是在3.x中,lucene的API中这个类已经被废 弃了,无法再找到。如果你的项目使用的是lucene是3.x,但是你又一定要使用QueryFilter,那么你必须自己创建一个 QueryFilter类,然后将2.x中QueryFilter的源代码复制过来。你可能会说,直接在工程中同时使用lucene2.x和3.x的核心 jar文件不就行了吗。但遗憾的是,一个工程下,是不能同时使用不同版本的lucene的。

2.2.QueryFilter方案

上文已经说了,如果一定要使用QueryFilter,由于lucene2.x中没有QueryFilter的API,所以自己要写一个QueryFilter,QueryFilter的源代码在lucene2.x中是这样的:

  1. import org.apache.lucene.search.CachingWrapperFilter;
  2. import org.apache.lucene.search.Query;
  3. import org.apache.lucene.search.QueryWrapperFilter;
  4. public class QueryFilter extends CachingWrapperFilter {
  5. /**
  6. * Constructs a filter which only matches documents matching
  7. * <code>query</code>.
  8. */
  9. public QueryFilter(Query query) {
  10. super(new QueryWrapperFilter(query));
  11. }
  12. public boolean equals(Object o) {
  13. return super.equals((QueryFilter) o);
  14. }
  15. public int hashCode() {
  16. return super.hashCode() ^ 0x923F64B9;
  17. }
  18. }

第一种方案的例子程序如下:

  1. //简单实现对keyword的搜索
  2. public static void search(String keyword) throws IOException, ParseException {
  3. QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());
  4. Query query = queryParser.parse(keyword.trim());
  5. QueryFilter filter = new QueryFilter(query);
  6. //检索
  7. search(query, filter);
  8. }
  9. //在搜索oldKeyword的结果集中搜索newKeyword
  10. public static void searchInResult(String newKeyword, String oldKeyword) throws ParseException, IOException {
  11. QueryParser queryParser = new QueryParser("content",new SimpleAnalyzer());
  12. Query query = queryParser.parse(newKeyword.trim());
  13. Query oldQuery = queryParser.parse(oldKeyword.trim());
  14. QueryFilter oldFilter = new QueryFilter(oldQuery);
  15. CachingWrapperFilter filter = new CachingWrapperFilter(oldFilter);
  16. //检索
  17. search(query, filter);
  18. }
  19. private static void search(Query query, Filter filter) throws IOException, ParseException {
  20. IndexSearcher ins = new IndexSearcher("d:/tesindex");
  21. Hits hits = ins.search(query, filter);
  22. for (int i = 0; i < hits.length(); i++) {
  23. Document doc = hits.doc(i);
  24. System.out.println(doc.get("content"));
  25. }
  26. }

2.3.BooleanQuery方案

使用BooleanQuery来实现在结果中检索的过程是这样的,首先通过关键字keyword1正常检索,当用户需要在检索结果中再通过关键字 keyword2检索的时候,通过构建BooleanQuery,来实现对在结果中检索的效果。这里要注意,这两个关键字都要使用 BooleanClause.Occur.MUST。

  1. //创建BooleanQuery
  2. BooleanQuery booleanQuery = new BooleanQuery();
  3. //多域检索,在这四个域中检索
  4. String[] fields = { "phoneType", "name", "category","free" };
  5. Query multiFieldQuery = new MultiFieldQueryParser(Version.LUCENE_36, fields, analyzer).parse(keyword);
  6. //将multiFieldQuery添加到BooleanQuery中
  7. booleanQuery.add(multiFieldQuery, BooleanClause.Occur.MUST);
  8. //如果osKeyword不为空
  9. if(osKeyword != null && !osKeyword.equals("") && !osKeyword.equals("null")){
  10. TermQuery osQuery = new TermQuery(new Term("phoneType",osKeyword));
  11. //将osQuery添加到BooleanQuery中
  12. booleanQuery.add(osQuery, BooleanClause.Occur.MUST);
  13. }

3.检索结果分页

3.1.两种方案

通过关键字的检索,当lucene返回多条记录的时候,往往一个页面是无法容纳所有检索结果的,这自然而然就该分页了。我这里给出两种方案,这两种方法我都是用过。

第一种方法,就是讲检索结果全部封装在一个Collection中,例如List中,将这个结果传到前台,如jsp页面。然后在这个list中进行分页显示;

第二种方法,是使用lucene自带的分页工具public TopDocs topDocs(int start,int howMany)。

我认为,第一种方法不涉及二次查询,这样的话就避免了在查询上的浪费。但是当检索的结果数据量很大,这样一次性传输这么多数据到客户端,而用户检索后得到 的结果往往只会查看第一页的内容,很少去查看第二页、第三页以及后面的内容,所以一次性将全部结果传到前台,这样的浪费是很大的。

第二种方法,虽然每次翻页都意味着一次查询,表面上浪费了资源,但是由于lucene的高效,这样的浪费对整个系统的影响是微乎其微的,但是这个方法避免了方法一中的缺陷。

3.2.分页实现

  1. /**
  2. * 对搜索返回的前n条结果进行分页显示
  3. * @param keyWord       查询关键词
  4. * @param pageSize      每页显示记录数
  5. * @param currentPage   当前页
  6. */
  7. public void paginationQuery(String keyWord,int pageSize,int currentPage) throws ParseException, CorruptIndexException, IOException {
  8. String[] fields = {"title","content"};
  9. QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36,fields,analyzer);
  10. Query query = queryParser.parse(keyWord);
  11. IndexReader indexReader  = IndexReader.open(directory);
  12. IndexSearcher indexSearcher = new IndexSearcher(indexReader);
  13. //TopDocs 搜索返回的结果
  14. TopDocs topDocs = indexSearcher.search(query, 100);//只返回前100条记录
  15. int totalCount = topDocs.totalHits; // 搜索结果总数量
  16. ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 搜索返回的结果集合
  17. //查询起始记录位置
  18. int begin = pageSize * (currentPage - 1) ;
  19. //查询终止记录位置
  20. int end = Math.min(begin + pageSize, scoreDocs.length);
  21. //进行分页查询
  22. for(int i=begin;i<end;i++) {
  23. int docID = scoreDocs[i].doc;
  24. Document doc = indexSearcher.doc(docID);
  25. int id = NumericUtils.prefixCodedToInt(doc.get("id"));
  26. String title = doc.get("title");
  27. System.out.println("id is : "+id);
  28. System.out.println("title is : "+title);
  29. }
  30. }

4.高亮检索结果

针对检索结果的高亮实现方法,在lucene中提供了响应的工具,这里使用lucene-highlighter-3.6.2.jar来实现对检索结果的高亮显示。

  1. public void search(String fieldName, String keyword)throws CorruptIndexException, IOException, ParseException {
  2. searcher = new IndexSearcher(indexPath);
  3. QueryParser queryParse = new QueryParser(fieldName, analyzer); // 构造QueryParser,解析用户输入的检索关键字
  4. Query query = queryParse.parse(keyword);
  5. Hits hits = searcher.search(query);
  6. for (int i = 0; i < hits.length(); i++) {
  7. Document doc = hits.doc(i);
  8. String text = doc.get(fieldName);
  9. SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color=‘red‘>", "</font>");
  10. Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
  11. highlighter.setTextFragmenter(new SimpleFragmenter(text.length()));
  12. if (text != null) {
  13. TokenStream tokenStream = analyzer.tokenStream(fieldName,new StringReader(text));
  14. String highLightText = highlighter.getBestFragment(tokenStream,text);
  15. System.out.println("高亮显示第 " + (i + 1) + " 条检索结果如下所示:");
  16. System.out.println(highLightText);
  17. }
  18. }
  19. searcher.close();
  20. }

上文的一行判断语句很重要:if(text != null),如果text为空,那么显示结果不但没有被高亮,而且得到的原始结果也会被过滤。可以再代码中加上,如果text==null,则让将原始检索结果赋给text,从而将结果显示出来。

5.检索结果的评分

lucene的评分是有一套自己的机制的,输入某一个关键字,lucene会对命中的记录进行评分,默认情况下,分数越高的结果会排在结果的越前面。如果 在创建索引的时候,没有对某个域进行加权,那么默认分数的上限是5分,如果有对域做加权,检索结果的评分可能会出现大于5分的情况。

我们可以使用explain()来看看lucene对检索结果的评分情况:

  1. //评分
  2. Explanation explanation = indexSearcher.explain(query, docID);
  3. System.out.println(explanation.toString());

在后台打印出来的信息如下:

  1. 2.4342022 = (MATCH) weight(name:books in 71491), product of:
  2. 0.2964393 = queryWeight(name:books), product of:
  3. 8.21147 = idf(docFreq=109, maxDocs=149037)
  4. 0.036100637 = queryNorm
时间: 2024-08-03 08:08:35

Lucene的多域查询、结果中查询、查询结果分页、高亮查询结果和结果评分的相关文章

三种方法实现从“一个(组)查询过程中返回两个表的查询结果”

还记得開始做机房的时候,遇到了要从一个函数中返回两个表的查询结果.当时的解决方法非常"冲动"也非常"无拘无束",直接在实体类里边加入了其它表的实体,效果是达到了,但总认为不伦不类. 如今介绍三种解决上述问题的方法(代码为VB.net.系统使用三层架构). 题设要求:如果我如今要从卡表和学生表里返回查询信息(卡表的comment,money,status和学生表的所有信息),卡表和学生表例如以下: 图一  学生表 图二    卡表 方法一:视图. 比較简单,相信这样的

TP 查询语句中如何使用 FIND_IN_SET 这样的查询方法

$condition['_string'] = 'FIND_IN_SET('.$citys.',city)';

如何查询AD中被设置为密码永不过期的域用户账号?

如何查询AD中被设置为密码永不过期的域用户账号? ?Lander Zhang 专注外企IT基础架构运维服务,IT Helpdesk 实战培训践行者博客:https://blog.51cto.com/lander51CTO讲师首页:https://edu.51cto.com/lecturer/733218.html轻松进外企:IT Helpdesk工程师实战自学之路博文介绍:https://blog.51cto.com/lander/2413018视频课程专题系列:https://edu.51ct

Centos中查询目录中内容命名ls

首先解释下这块, root代表当前登录用户,localhost代表主机名, ~代表当前主机目录,  #代表用户权限   #表示超级用户,$表示普通用户: 查询目录中内容命令 ls  (list缩写) 格式 ls [选项]  [文件或目录] 选项: -a 显示所有文件,包括隐藏文件 -l  显示详细信息 -d 查看目录属性 -h 人性化显示文件大小 -i  显示inode 超级用户root默认的当前目录是 root目录 我们可以用pwd命名(Print Working Directory 打印当前

多表查询,子查询,及查询语句中语句执行的先后顺序和特点

对多张表进行查询操作 查询方式1: 笛卡尔积查询 select * from 表1,表2; 结果:让表1中的每一条记录和表2中每一条记录一次组合 表1 n条记录 表2 m条记录 最终查询结果:n * m 条 注意: 1.观察笛卡尔积查询的最终结果,很多数据没有实际作用 2.查询结果太多,对内存压力大 笛卡尔积查询优化(内连接) 加上条件 隐式内连接 select * from 表1,表2 where 表1.字段 = 表2.字段; 显示内连接语法 select * from 表1 inner jo

查询score中选学多门课程的同学中分数为非最高分成绩的记录。

20.查询score中选学多门课程的同学中分数为非最高分成绩的记录. select * from score a where sno in ( select sno from score group by sno having count(1)>1) and a.degree<(select max(degree) from score b where a.cno=b.cno  )

[示例]NSPredicate基础-查询数组中负荷条件的子集

代码: #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { /* 简述:Cocoa框架中的NSPredicate用于查询,原理和用法都类似于SQL中的where,作用相当于数据库的过滤取. 定义(最常用到的方法): NSPredicate这个类有点类似于数据库中的查询,是用于在一批内容中查询符合条件的子集,中文翻译成“谓词”.这个翻译实在让我感觉很别扭,虽然

查询字符串中字母的个数(两种实现方式1,list与set集合 2,map集合)

题目: 取出一个字符串中字母出现的次数.如:字符串:"abcde%^kka27qoq" ,输出格式为:a(2)b(1)k(2)... 第一种方式(set和list结合使用): package itheima; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * .取出一个字符串中字母出现的次数.如:字符串:"

每日学习心得:SharePoint 为列表中的文件夹添加子项(文件夹)、新增指定内容类型的子项、查询列表中指定的文件夹下的内容

前言: 这里主要是针对列表中的文件下新增子项的操作,同时在新建子项时,可以为子项指定特定的内容类型,在某些时候需要查询指定的文件夹下的内容,针对这些场景都一一给力示例和说明,都是一些很小的知识点,希望能够对大家有所帮助. 1.   在列表中为列表项添加子项 为列表添加子项大家都很熟悉,但是如何为列表项添加子项呢?例如列表项是一个文件夹,如何为该文件夹添加子项呢?这里就用到了List.AddItem()方法,具体示例如下: 首先我们要获取列表中的子项: SPListItem root_item=l