2.5 Lucene查询详解
文档索引完成以后就可以对其进行搜索,当用户输入一个关键字,搜索引擎接收到后,并不是立刻就将它放入后台开始进行关键字的检索,而应当首先对这个关键字进行一定的分析和处理,使之成为一种后台可以理解的形式,只有这样,才能提高检索的效率,同时检索出更加有效的结果。
2.5.1 搜索入门
在Lucene中,处理用户输入的查询关键词其实就是构建Query对象的过程。Lucene搜索文档需要实例化一个IndexSearcher对象,IndexSearcher对象的search()方法完成搜索过程,Query对象作为search()方法的对象。搜索结果会保存在一个TopDocs类型的文档集合中,遍历TopDocs集合输出文档信息。见代码清单2-11。
代码清单2-11
package tup.lucene.queries; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import tup.lucene.ik.IKAnalyzer6x; public class QueryParseTest{ public static void main(String[] args) throws ParseException, IOException { String field = "title"; Path indexPath = Paths.get("indexdir"); Directory dir = FSDirectory.open(indexPath); IndexReader reader = DirectoryReader.open(dir); IndexSearcher searcher = new IndexSearcher(reader); Analyzer analyzer = new IKAnalyzer6x(); QueryParser parser = new QueryParser(field, analyzer); parser.setDefaultOperator(Operator.AND); Query query = parser.parse("农村学生"); // 查询关键词 System.out.println("Query:"+query.toString()); // 返回前10条 TopDocs tds = searcher.search(query, 10); for(ScoreDoc sd : tds.scoreDocs){ Document doc = searcher.doc(sd.doc); System.out.println("DocID:" + sd.doc); System.out.println("id:" + doc.get("id")); System.out.println("title:" + doc.get("title")); System.out.println("文档评分:" + sd.score); } dir.close(); reader.close(); } }
注意上面代码中的这几行代码:
QueryParser parser = new QueryParser(field, analyzer); Query query = parser.parse("农村学生"); parser.setDefaultOperator(Operator.AND); TopDocs topDocs = searcher.search(query, 10);
QueryParser实际上就是一个解析用户输入的工具,可以通过扫描用户输入的字符串生成Query对象。当使用QueryParser构建用户Query时,要搜索的field和analyzer对象作为参数传入QueryParser类,告诉QueryParser在哪个字段内查找该关键字信息以及搜索时使用什么样的分词器。这里设置要查询的字段为title字段,查询所用的分词器为IK智能分词,查询关键词为“农村学生”,关键词经过分词器分成“农村”和“学生”两个词项,title中含有这两个词项中的任何一个的文档都是本次查询的匹配文档。如果只想返回同时包含两个词项的文档,可以通过setDefaultOperator()方法把关键词理解为AND操作。
注意,在结果中打印了DocID和id,前者是文档ID,是Lucene为索引的每个文档做的标记,后者是文档内部的id字段。
运行结果如下:
加载扩展词典:ext.dic 加载扩展停止词典:stopword.dic 加载扩展停止词典:ext_stopword.dic Query:+title:农村 +title:学生 DocID:1 id:2 title:北大迎4380名新生 农村学生700多人近年最多 文档评分:1.6022166
改变上面搜索实例的Query对象就可以实现不同种类型的搜索需求,比如多域查询、布尔查询、模糊查询、通配符查询等。
2.5.2 多域搜索(MultiFieldQueryParser)
2.4.1小节中介绍的QueryParser可以搜索单个字段,而MultiFieldQueryParser则可以查询多个字段。通过MultiFieldQueryParser对象生成Query对象的代码如下:
String[] fields = { "title", "content" }; Analyzer analyzer = new IKAnalyzer6x(true); MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer); Query multiFieldQuery = parser.parse("日本");
通过IndexSearcher搜索文档和打印结果的代码和2.4.1中的一样,这里省略,给出查询结果:
title:日本content:日本 DocID:0 id:1 title:安倍晋三本周会晤特朗普 将强调日本对美国益处 content:日本首相安倍晋三计划2月10日在华盛顿与美国总统特朗普举行会晤时提出加 大日本在美国投资的设想 文档评分:2.0341508
2.5.3 词项搜索(TermQuery)
TermQuery是最简单也是最常用的Query。TermQuery可以理解成为“词项搜索”,在搜索引擎中最基本的搜索就是在索引中搜索某一词条,而TermQuery就是用来完成这项工作的。在Lucene中词条是最基本的搜索单位,从本质上来讲一个词条其实就是一个key/value对。只不过这个key是字段名,而value则表示字段中所包含的某个关键字。要使用TermQuery进行搜索首先需要构造一个Term对象,示例代码如下:
Term term = new Term("title", "美国");
然后使用Term对象为参数来构造一个TermQuery对象,代码设置如下:
Query termQuery = new TermQuery(term);
这样所有在“title”字段中包含有“美国”的文档都会在使用TermQuery进行查询时作为符合查询条件的结果返回。同样省略IndexSearcher搜索文档和打印结果的代码,运行结果如下:
Query:title:美国 DocID:2 id:3 title:特朗普宣誓就任美国第45任总统 文档评分:0.53872687 DocID:0 id:1 title:安倍晋三本周会晤特朗普 将强调日本对美国益处 文档评分:0.38388318
2.5.4 布尔搜索(BooleanQuery)
BooleanQuery也是实际开发过程中经常使用的一种Query查询,它其实是一个组合的Query,在使用时可以把各种Query对象添加进去并标明它们之间的逻辑关系。BooleanQuery本身来讲是一个布尔子句的容器,它提供了专门的API方法往其中添加子句,并标明它们之间的关系。下面的代码中,创建了两个TermQuery, BooleanClause对象可以指定查询的包含关系,并作为参数通过BooleanQuery.Builder()构造布尔查询。查询title字段中包含关键词“美国”并且“content”字段中不包含日本的文档,代码如下:
Query query1 = new TermQuery(new Term("title","美国")); Query query2 = new TermQuery(new Term("content","日本")); BooleanClause bc1=new BooleanClause(query1,Occur.MUST); BooleanClause bc2=new BooleanClause(query2,Occur.MUST_NOT); BooleanQuery boolQuery=new BooleanQuery.Builder() .add(bc1).add(bc2).build();
查询结果如下:
Query:+title:美国 -content:日本 DocID:2 id:1 title:特朗普宣誓就任美国第45任总统 content:当地时间1月20日,唐纳德·特朗普在美国国会宣誓就职,正式成为美国第45任总统。 文档评分:0.53872687
2.5.5 范围搜索(RangeQuery)
有时用户需要查找满足一定范围的文档,比如查找某一时间段内的所有文档,Lucene提供了RangeQuery查询来满足这种需求。
RangeQuery表示在某范围内的搜索条件,实现从一个开始词条到一个结束词条的搜索功能,在查询时“开始词条”和“结束词条”可以包含在内也可以不被包含在内。查询新闻回复条数在500条到1000条之间的有哪些,构成Query对象的代码如下:
Query rangeQuery=IntPoint.newRangeQuery("reply",500,1000);
同样省略IndexSearcher搜索文档和打印结果的代码,查询结果如下:
Query:reply:[500 TO 1000] DocID:0 id:1 title:安倍晋三本周会晤特朗普 将强调日本对美国益处 Reply:672 文档评分:1.0 DocID:1 id:2 title:北大迎4380名新生 农村学生700多人近年最多 Reply:995 文档评分:1.0
2.5.6 前缀搜索(PrefixQuery)
PrefixQuery就是使用前缀来进行查找的。通常情况下,首先定义一个词条Term。该词条包含要查找的字段名以及关键字的前缀,然后通过该词条构造一个PrefixQuery对象,就可以进行前缀查找了。查询包含以“学”开头的词项的文档,构造Query对象的代码如下:
Term term = new Term("title", "学"); Query prefixQuery = new PrefixQuery(term);
同样省略IndexSearcher搜索文档和打印结果的代码,查询结果如下:
Query:title:学* DocID:1 id:2 title:北大迎4380名新生 农村学生700多人近年最多 文档评分:1.0
2.5.7 多关键字搜索(PhraseQuery)
除了普通的TermQuery外,Lucene还提供了一种Phrase查询功能。用户在搜索引擎中进行搜索时,常常查找的并非是一个简单的单词,很有可能是几个不同的关键字。这些关键字之间要么是紧密相连的,成为一个精确的短语,要么在这几个关键字之间还插有其他无关的内容。
PhraseQuery正是Lucene所提供的满足上述需求的一种Query对象。它的add方法可以让用户向其内部添加关键字,在添加完毕后,用户还可以通过setSlop()方法来设定一个称之为“坡度”的变量来确定关键字之间是否允许或允许多少个无关词汇的存在。
PhraseQuery.Builder builder = new PhraseQuery.Builder(); builder.add(new Term("title", "日本"), 2); builder.add(new Term("title", "美国"), 3); PhraseQuery phraseQuery = builder.build(); 打印phraseQuery对象: Query:title:"? ?日本 美国"
2.5.8 模糊搜索(FuzzyQuery)
FuzzyQuery是一种模糊查询,它可以简单地识别两个相近的词语。例如,由于拼写错误把“Trump”拼成“Trmp”或者“Tramp”,使用FuzzyQuery仍可搜索到正确的结果,代码如下:
Term term = new Term("title", "Tramp"); FuzzyQuery fuzzyQuery = new FuzzyQuery(term); Query:title:Tramp~2 DocID:2 id:3 title:特朗普宣誓(Donald Trump)就任美国第45任总统 文档评分:0.6056149
2.5.9 通配符搜索(WildcardQuery)
Lucene也提供了通配符的查询,就是使用WildcardQuery。示例如下:
WildcardQuery wildcardQuery=new WildcardQuery(new Term(field, "学?")); Query:title:学* DocID:1 id:2 title:北大迎4380名新生 农村学生700多人近年最多 文档评分:1.0