一、搜索流程详解
1. 先看一下Lucene的架构图

由图可知搜索的过程如下:
用户输入搜索的关键字、对关键字进行分词、根据分词结果去索引库里面找到对应的文章id、根据文章id找到对应的文章
2. Lucene搜索API 图示

3. Lucene搜索代码示例
先在pom.xml里面引入查询分析器模块
1<!-- lucene-queryparser 查询分析器模块 --> 2 <dependency> 3 <groupId>org.apache.lucene</groupId> 4 <artifactId>lucene-queryparser</artifactId> 5 <version>7.3.0</version> 6 </dependency> 7 8package com.study.lucene.searchdetail; 9 10import java.io.IOException; 11import java.nio.file.Paths; 12import java.text.ParseException; 13 14import org.apache.lucene.analysis.Analyzer; 15import org.apache.lucene.document.Document; 16import org.apache.lucene.index.DirectoryReader; 17import org.apache.lucene.index.IndexReader; 18import org.apache.lucene.queryparser.classic.QueryParser; 19import org.apache.lucene.search.IndexSearcher; 20import org.apache.lucene.search.Query; 21import org.apache.lucene.search.ScoreDoc; 22import org.apache.lucene.search.TopDocs; 23import org.apache.lucene.store.Directory; 24import org.apache.lucene.store.FSDirectory; 25 26import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 27 28/** 29 * @Description: lucene 搜索基本流程示例 30 * @author liguangsheng 31 * @date 2018年5月11日 32 * 33 */ 34 35public class SearchBaseFlow { 36 37 public static void main(String[] args) 38 throws IOException, ParseException, org.apache.lucene.queryparser.classic.ParseException { 39 // 使用的分词器 40 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 41 // 索引存储目录 42 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 43 // 索引读取器 44 IndexReader indexReader = DirectoryReader.open(directory); 45 // 索引搜索器 46 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 47 // 要搜索的字段 48 String filedName = "name"; 49 // 查询生成器(解析输入生成Query查询对象) 50 QueryParser parser = new QueryParser(filedName, analyzer); 51 // 通过parse解析输入(分词),生成query对象 52 Query query = parser.parse("Thinkpad"); 53 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 54 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 55 56 // 获得总命中数 57 System.out.println(topDocs.totalHits); 58 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 59 for (ScoreDoc sdoc : topDocs.scoreDocs) { 60 // 根据文档id取存储的文档 61 Document hitDoc = indexSearcher.doc(sdoc.doc); 62 // 取文档的字段 63 System.out.println(hitDoc.get(filedName)); 64 } 65 66 // 使用完毕,关闭、释放资源 67 indexReader.close(); 68 directory.close(); 69 } 70}
示例代码的图解:

二、搜索核心API详解
1. IndexReader 索引读取器
Open一个读取器,读取的是该时刻点的索引视图。如果后续索引发生改变,需重新open一个读取器。获得索引读取器的方式:
DirectoryReader.open(IndexWriter indexWriter) 优先使用
DirectoryReader.open(Directory)
DirectoryReader.openIfChanged(DirectoryReader) 共享当前reader资源重新打开一个(当索引变化时)
IndexReader分为两类:

复合读取器,多个读取器的复合。只可直接用它获取stored fields 。在内部通过CompositeReader.getSequentialSubReaders 得到里面的叶子读取器来获取其他数据
叶子读取器:支持获取stored fields, doc values, terms(词项), and postings (词项对应的文档)
DirectoryReader 是 复合读取器
IndexReader 主要API:

LeafReader 主要API:

2. IndexSearcher 索引搜索器
应用通过调用它的search(Query,int)重载方法在一个IndexReader上实现搜索。出于性能的考虑,请使用一个IndexSearcher实例,除非索引发生变化。如索引更新了则通过DirectoryReader.openIfChanged(DirectoryReader) 取得新的读取器,再创建新的搜索器。
IndexSearcher 索引搜索器 API
搜索 API:

获取文档 API:

TopDocs 搜索命中的结果集 (Top-N)

TopFieldDocs 按字段排序的搜索命中结果集

ScoreDoc

三、基本查询详解
Query 查询的表示,它的可实例化子类有
要掌握的基本查询

1、TermQuery 词项查询
最基本、最常用的查询。用来查询指定字段包含指定词项的文档。
TermQuery tq = new TermQuery(new Term(“name", “thinkpad"));
2、BooleanQuery 布尔查询
搜索的条件往往是多个的,如要查询名称包含“电脑” 或 “thinkpad”的商品,就需要两个词项查询做或合并。布尔查询就是用来组合多个子查询的。每个子查询称为布尔字句 BooleanClause,布尔字句自身也可以是组合的。 组合关系支持如下四种:
Occur.SHOULD 或
Occur.MUST 且
Occur.MUST_NOT 且非
Occur.FILTER 同 MUST,但该字句不参与评分
布尔查询默认的最大字句数为1024,在将通配符查询这样的查询rewriter为布尔查询时,往往会产生很多的字句,可能抛出TooManyClauses 异常。可通过BooleanQuery.setMaxClauseCount(int)设置最大字句数。
1// 布尔查询 2Query query1 = new TermQuery(new Term(filedName, "thinkpad")); 3Query query2 = new TermQuery(new Term("simpleIntro", "英特尔")); 4BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder(); 5booleanQueryBuilder.add(query1, Occur.SHOULD); 6booleanQueryBuilder.add(query2, Occur.MUST); 7BooleanQuery booleanQuery = booleanQueryBuilder.build(); 8 9// 可像下一行这样写 10// BooleanQuery booleanQuery = new BooleanQuery.Builder() 11// .add(query1, Occur.SHOULD).add(query2, Occur.MUST).build();
3、PhraseQuery 短语查询
最常用的查询,匹配特定序列的多个词项。PhraserQuery使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配,默认为0。有两种方式来构建对象:

注意:所有加入的词项都匹配才算匹配(即使是你在同一位置加入多个词项)。如果需要在同一位置匹配多个同义词中的一个,适合用MultiPhraseQuery
1PhraseQuery phraseQuery1 = new PhraseQuery("name", "thinkpad", 2 "carbon"); 3 4PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad", 5 "carbon"); 6 7PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); 8 9PhraseQuery phraseQuery4 = new PhraseQuery.Builder() 10 .add(new Term("name", "笔记本电脑"), 4) 11 .add(new Term("name", "联想"), 5).build(); 12// 这两句等同 13PhraseQuery phraseQuery5 = new PhraseQuery.Builder() 14 .add(new Term("name", "笔记本电脑"), 0) 15 .add(new Term("name", "联想"), 1).build();
PhraseQuery slop 移动因子说明:
String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";
A、如果想用 “thinkpad carbon” 来匹配 name。因中间有 x1,则需要将thinkpad 向右移动1个位置。
B、如果想用 “carbon thinkpad” 来匹配 name。因中间有 x1,则需要将carbon 向右移动3个位置。
1// String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; 2 3// PhraseQuery 短语查询 4PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad","carbon"); 5// slop示例 6PhraseQuery phraseQuery2Slop = new PhraseQuery(3, "name", "carbon", "thinkpad"); 7PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); 8// slop示例 9PhraseQuery phraseQuery3Slop = new PhraseQuery(2, "name", "联想","笔记本电脑");
4、MultiPhraseQuery 多重短语查询
短语查询的一种更通用的用法,支持同位置多个词的OR匹配。通过里面的Builder来构建MultiPhraseQuery:

1// 4 MultiPhraseQuery 多重短语查询 2Term[] terms = new Term[2]; 3terms[0] = new Term("name", "笔记本"); 4terms[1] = new Term("name", "笔记本电脑"); 5Term t = new Term("name", "联想"); 6MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder() 7 .add(terms).add(t).build(); 8 9// 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,所以查不出。 10PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0) 11 .add(terms[1], 0).add(t, 1).build();
前面4个查询的完整示例代码:
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.index.DirectoryReader; 9import org.apache.lucene.index.IndexReader; 10import org.apache.lucene.index.Term; 11import org.apache.lucene.queryparser.classic.ParseException; 12import org.apache.lucene.search.BooleanClause.Occur; 13import org.apache.lucene.search.BooleanQuery; 14import org.apache.lucene.search.IndexSearcher; 15import org.apache.lucene.search.MultiPhraseQuery; 16import org.apache.lucene.search.PhraseQuery; 17import org.apache.lucene.search.Query; 18import org.apache.lucene.search.ScoreDoc; 19import org.apache.lucene.search.TermQuery; 20import org.apache.lucene.search.TopDocs; 21import org.apache.lucene.store.Directory; 22import org.apache.lucene.store.FSDirectory; 23 24import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 25 26/** 27 * @Description: 搜索查询示例 28 * @author liguangsheng 29 * @date 2018年5月12日 30 * 31 */ 32 33public class SearchQueryDemo { 34 35 public static void main(String[] args) throws IOException, ParseException { 36 // 使用的分词器 37 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 38 // 索引存储目录 39 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 40 // 索引读取器 41 IndexReader indexReader = DirectoryReader.open(directory); 42 // 索引搜索器 43 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 44 // 要搜索的字段 45 String filedName = "name"; 46 47 // 1、词项查询 48 Query query1 = new TermQuery(new Term(filedName, "thinkpad")); 49 System.out.println("************** 词项查询 ******************"); 50 doSearch(query1, indexSearcher); 51 52 // 2、布尔查询 53 Query query2 = new TermQuery(new Term("simpleIntro", "英特尔")); 54 BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder(); 55 booleanQueryBuilder.add(query1, Occur.SHOULD); 56 booleanQueryBuilder.add(query2, Occur.MUST); 57 BooleanQuery booleanQuery = booleanQueryBuilder.build(); 58 59 // 可像下一行这样写 60 // BooleanQuery booleanQuery = new BooleanQuery.Builder() 61 // .add(query1, Occur.SHOULD).add(query2, Occur.MUST).build(); 62 63 System.out.println("************** 布尔查询 ******************"); 64 doSearch(booleanQuery, indexSearcher); 65 66 // 3、PhraseQuery 短语查询 67 // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; 68 PhraseQuery phraseQuery1 = new PhraseQuery("name", "thinkpad", "carbon"); 69 System.out.println("************** phrase 短语查询 ******************"); 70 doSearch(phraseQuery1, indexSearcher); 71 72 PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad", "carbon"); 73 System.out.println("************** phrase 短语查询 ******************"); 74 doSearch(phraseQuery2, indexSearcher); 75 76 // slop示例 3表示最大可以移动的位置,移动的过程中只要匹配短语carbon thinkpad即可 77 PhraseQuery phraseQuery2Slop = new PhraseQuery(3, "name", "carbon", "thinkpad"); 78 System.out.println("********** phrase slop 短语查询 ***************"); 79 doSearch(phraseQuery2Slop, indexSearcher); 80 81 PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); 82 System.out.println("************** phrase 短语查询 ******************"); 83 doSearch(phraseQuery3, indexSearcher); 84 85 // slop示例 86 PhraseQuery phraseQuery3Slop = new PhraseQuery(2, "name", "联想", "笔记本电脑"); 87 System.out.println("************** phrase s 短语查询 ******************"); 88 doSearch(phraseQuery3Slop, indexSearcher); 89 90 PhraseQuery phraseQuery4 = new PhraseQuery.Builder().add(new Term("name", "笔记本电脑"), 4) // 4、5是这个词的位置,和 0、1等同 91 .add(new Term("name", "联想"), 5).build(); 92 System.out.println("********** phrase Builder 1 短语查询 **************"); 93 doSearch(phraseQuery4, indexSearcher); 94 95 // 等同 phraseQuery4 96 PhraseQuery phraseQuery5 = new PhraseQuery.Builder().add(new Term("name", "笔记本电脑"), 0) // 4、5是这个词的位置,和 0、1等同 97 .add(new Term("name", "联想"), 1).build(); 98 System.out.println("*********** phrase Builder 2 短语查询 ***********"); 99 doSearch(phraseQuery5, indexSearcher); 100 101 // 4 MultiPhraseQuery 多重短语查询 102 Term[] terms = new Term[2]; 103 terms[0] = new Term("name", "笔记本"); 104 terms[1] = new Term("name", "笔记本电脑"); 105 Term t = new Term("name", "联想"); 106 MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder().add(terms).add(t).build(); 107 System.out.println("************** multiPhraseQuery 短语查询 ******************"); 108 doSearch(multiPhraseQuery, indexSearcher); 109 110 // 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,所以查不出。 111 PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0).add(terms[1], 0).add(t, 1).build(); 112 System.out.println("************** multiPhraseQuery 对比 PhraseQuery 短语查询 ******************"); 113 doSearch(pquery, indexSearcher); 114 115 // 使用完毕,关闭、释放资源 116 indexReader.close(); 117 directory.close(); 118 } 119 120 private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { 121 // 打印输出查询 122 System.out.println("query: " + query.toString()); 123 124 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 125 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 126 127 System.out.println("**** 查询结果 "); 128 // 获得总命中数 129 System.out.println("总命中数:" + topDocs.totalHits); 130 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 131 for (ScoreDoc sdoc : topDocs.scoreDocs) { 132 // 根据文档id取存储的文档 133 Document hitDoc = indexSearcher.doc(sdoc.doc); 134 System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); 135 // 取文档的字段 136 System.out.println("prodId:" + hitDoc.get("prodId")); 137 System.out.println("name:" + hitDoc.get("name")); 138 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 139 System.out.println("price:" + hitDoc.get("price")); 140 141 System.out.println(); 142 } 143 144 } 145}
5、SpanNearQuery 临近查询(跨度查询)
用于更复杂的短语查询,可以指定词间位置的最大间隔跨度。通过组合一系列的SpanQuery 实例来进行查询,可以指定是否按顺序匹配、slop、gap。

1// SpanNearQuery 临近查询 2SpanTermQuery tq1 = new SpanTermQuery(new Term("name", "thinkpad")); 3SpanTermQuery tq2 = new SpanTermQuery(new Term("name", "carbon")); 4SpanNearQuery spanNearQuery = new SpanNearQuery( 5 new SpanQuery[] { tq1, tq2 }, 1, true); 6 7// SpanNearQuery 临近查询 gap slop 使用 8SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery 9 .newOrderedNearQuery("name"); 10spanNearQueryBuilder.addClause(tq1).addGap(0).setSlop(1) 11 .addClause(tq2); 12SpanNearQuery spanNearQuery5 = spanNearQueryBuilder.build();
完整代码示例
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.index.DirectoryReader; 9import org.apache.lucene.index.IndexReader; 10import org.apache.lucene.index.Term; 11import org.apache.lucene.queryparser.classic.ParseException; 12import org.apache.lucene.search.IndexSearcher; 13import org.apache.lucene.search.Query; 14import org.apache.lucene.search.ScoreDoc; 15import org.apache.lucene.search.TopDocs; 16import org.apache.lucene.search.spans.SpanNearQuery; 17import org.apache.lucene.search.spans.SpanQuery; 18import org.apache.lucene.search.spans.SpanTermQuery; 19import org.apache.lucene.store.Directory; 20import org.apache.lucene.store.FSDirectory; 21 22import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 23 24 25/** 26 * @Description: SpanNearQuery 临近查询(跨度查询) 27 * @author liguangsheng 28 * @date 2018年5月12日 29 * 30 */ 31public class SpanNearQueryDemo { 32 33 public static void main(String[] args) throws IOException, ParseException { 34 // 使用的分词器 35 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 36 // 索引存储目录 37 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 38 // 索引读取器 39 IndexReader indexReader = DirectoryReader.open(directory); 40 // 索引搜索器 41 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 42 43 // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; 44 45 // SpanNearQuery 临近查询 46 SpanTermQuery tq1 = new SpanTermQuery(new Term("name", "thinkpad")); 47 SpanTermQuery tq2 = new SpanTermQuery(new Term("name", "carbon")); 48 SpanNearQuery spanNearQuery = new SpanNearQuery( 49 new SpanQuery[] { tq1, tq2 }, 1, true); 50 51 System.out.println("************** SpanNearQuery 临近查询 ************"); 52 doSearch(spanNearQuery, indexSearcher); 53 54 // 下面的例子词是反序的 55 SpanNearQuery spanNearQuery2 = new SpanNearQuery( 56 new SpanQuery[] { tq2, tq1 }, 1, true); 57 58 System.out.println( 59 "************** SpanNearQuery 临近查询 2 1,true************"); 60 doSearch(spanNearQuery2, indexSearcher); 61 62 SpanNearQuery spanNearQuery3 = new SpanNearQuery( 63 new SpanQuery[] { tq2, tq1 }, 3, true); 64 65 System.out.println( 66 "************** SpanNearQuery 临近查询 3 3, true************"); 67 doSearch(spanNearQuery3, indexSearcher); 68 69 SpanNearQuery spanNearQuery4 = new SpanNearQuery( 70 new SpanQuery[] { tq2, tq1 }, 3, false); 71 72 System.out.println( 73 "************** SpanNearQuery 临近查询 4 3, false************"); 74 doSearch(spanNearQuery4, indexSearcher); 75 76 // SpanNearQuery 临近查询 gap slop 使用 1 77 SpanTermQuery ctq1 = new SpanTermQuery(new Term("name", "张三")); 78 SpanTermQuery ctq2 = new SpanTermQuery(new Term("name", "在理")); 79 SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery 80 .newOrderedNearQuery("name"); 81 spanNearQueryBuilder.addClause(ctq1).addGap(0).setSlop(2) 82 .addClause(ctq2); 83 84 System.out.println("************** SpanNearQuery 临近查询 ************"); 85 doSearch(spanNearQueryBuilder.build(), indexSearcher); 86 87 // SpanNearQuery 临近查询 gap slop 使用 2 88 SpanNearQuery.Builder spanNearQueryBuilder2 = SpanNearQuery 89 .newOrderedNearQuery("name"); 90 spanNearQueryBuilder2.addClause(ctq1).addGap(2).setSlop(0) 91 .addClause(ctq2); 92 93 System.out.println("************** SpanNearQuery 临近查询 ************"); 94 doSearch(spanNearQueryBuilder2.build(), indexSearcher); 95 96 // SpanNearQuery 临近查询 gap slop 使用 3 97 SpanNearQuery.Builder spanNearQueryBuilder3 = SpanNearQuery 98 .newOrderedNearQuery("name"); 99 spanNearQueryBuilder3.addClause(ctq1).addGap(1).setSlop(1) 100 .addClause(ctq2); 101 102 System.out.println("************** SpanNearQuery 临近查询 ************"); 103 doSearch(spanNearQueryBuilder3.build(), indexSearcher); 104 105 // 使用完毕,关闭、释放资源 106 indexReader.close(); 107 directory.close(); 108 } 109 110 private static void doSearch(Query query, IndexSearcher indexSearcher) 111 throws IOException { 112 // 打印输出查询 113 System.out.println("query: " + query.toString()); 114 115 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 116 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 117 118 System.out.println("**** 查询结果 "); 119 // 获得总命中数 120 System.out.println("总命中数:" + topDocs.totalHits); 121 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 122 for (ScoreDoc sdoc : topDocs.scoreDocs) { 123 // 根据文档id取存储的文档 124 Document hitDoc = indexSearcher.doc(sdoc.doc); 125 System.out.println("-------------- docId=" + sdoc.doc + ",score=" 126 + sdoc.score); 127 // 取文档的字段 128 System.out.println("prodId:" + hitDoc.get("prodId")); 129 System.out.println("name:" + hitDoc.get("name")); 130 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 131 System.out.println("price:" + hitDoc.get("price")); 132 133 System.out.println(); 134 } 135 136 } 137}
6、TermRangeQuery 词项范围查询
用于查询包含某个范围内的词项的文档,如以字母开头a到c的词项。词项在反向索引中是排序的,只需指定的开始词项、结束词项,就可以查询该范围的词项。 如果是做数值的范围查询则用 PointRangeQuery 。

参数说明:
第1个参数:要查询的字段-field
第2个参数::下边界词-lowerTerm
第3个参数:上边界词-upperTerm
第4个参数:是否包含下边界-includeLower
第5个参数:是否包含上边界 includeUpper
1// TermRangeQuery 词项范围查询 2TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name", 3 "carbon", "张三", false, true);
完整示例代码
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.index.DirectoryReader; 9import org.apache.lucene.index.IndexReader; 10import org.apache.lucene.queryparser.classic.ParseException; 11import org.apache.lucene.search.IndexSearcher; 12import org.apache.lucene.search.Query; 13import org.apache.lucene.search.ScoreDoc; 14import org.apache.lucene.search.TermRangeQuery; 15import org.apache.lucene.search.TopDocs; 16import org.apache.lucene.store.Directory; 17import org.apache.lucene.store.FSDirectory; 18 19import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 20 21 22/** 23 * @Description: TermRangeQuery 词项范围查询 24 * @author liguangsheng 25 * @date 2018年5月12日 26 * 27 */ 28public class TermRangeQueryDemo { 29 30 /** 31 * lucene 搜索查询示例 32 */ 33 public static void main(String[] args) throws IOException, ParseException { 34 // 使用的分词器 35 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 36 // 索引存储目录 37 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 38 // 索引读取器 39 IndexReader indexReader = DirectoryReader.open(directory); 40 // 索引搜索器 41 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 42 43 // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; 44 45 // TermRangeQuery 词项范围查询 46 TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name", 47 "carbon", "张三", false, true); 48 49 System.out.println("********** TermRangeQuery 词项范围查询 ***********"); 50 doSearch(termRangeQuery, indexSearcher); 51 52 // 使用完毕,关闭、释放资源 53 indexReader.close(); 54 directory.close(); 55 } 56 57 private static void doSearch(Query query, IndexSearcher indexSearcher) 58 throws IOException { 59 // 打印输出查询 60 System.out.println("query: " + query.toString()); 61 62 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 63 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 64 65 System.out.println("**** 查询结果 "); 66 // 获得总命中数 67 System.out.println("总命中数:" + topDocs.totalHits); 68 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 69 for (ScoreDoc sdoc : topDocs.scoreDocs) { 70 // 根据文档id取存储的文档 71 Document hitDoc = indexSearcher.doc(sdoc.doc); 72 System.out.println("-------------- docId=" + sdoc.doc + ",score=" 73 + sdoc.score); 74 // 取文档的字段 75 System.out.println("prodId:" + hitDoc.get("prodId")); 76 System.out.println("name:" + hitDoc.get("name")); 77 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 78 System.out.println("price:" + hitDoc.get("price")); 79 80 System.out.println(); 81 } 82 83 } 84}
7、PrefixQuery, WildcardQuery, RegexpQuery
PrefixQuery:前缀查询,查询包含以xxx为前缀的词项的文档,是通配符查询,如 app,实际是 app*
1// PrefixQuery 前缀查询 2PrefixQuery prefixQuery = new PrefixQuery(new Term("name", "think"));
WildcardQuery:通配符查询, *表示0个或多个字符,?表示1个字符,\是转义符。通配符查询可能会比较慢,不可以通配符开头(那样就是所有词项了)
1// WildcardQuery 通配符查询 2WildcardQuery wildcardQuery = new WildcardQuery( 3 new Term("name", "think*"));
RegexpQuery:正则表达式查询,词项符合某正则表达式
1// RegexpQuery 正则表达式查询 2RegexpQuery regexpQuery = new RegexpQuery(new Term("name", "厉害.{4}"));
注意:这三种查询可能会比较慢,使用时谨慎
8、FuzzyQuery 模糊查询
简单地与索引词项进行相近匹配,允许最大2个不同字符。常用于拼写错误的容错:如把 “thinkpad” 拼成 “thinkppd”或 “thinkd”,使用FuzzyQuery 仍可搜索到正确的结果。
1// FuzzyQuery 模糊查询 2FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("name", "thind")); 3 4FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("name", "thinkd"), 2); 5 6FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("name", "thinkpaddd")); 7 8FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("name", "thinkdaddd"));
7和8对应查询的完整示例代码
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.index.DirectoryReader; 9import org.apache.lucene.index.IndexReader; 10import org.apache.lucene.index.Term; 11import org.apache.lucene.queryparser.classic.ParseException; 12import org.apache.lucene.search.FuzzyQuery; 13import org.apache.lucene.search.IndexSearcher; 14import org.apache.lucene.search.PrefixQuery; 15import org.apache.lucene.search.Query; 16import org.apache.lucene.search.RegexpQuery; 17import org.apache.lucene.search.ScoreDoc; 18import org.apache.lucene.search.TopDocs; 19import org.apache.lucene.search.WildcardQuery; 20import org.apache.lucene.store.Directory; 21import org.apache.lucene.store.FSDirectory; 22 23import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 24 25 26/** 27 * @Description: PrefixQuery前缀查询/WildcardQuery通配符查询/RegexpQuery正则表达式查询/FuzzyQuery模糊查询 28 * @author liguangsheng 29 * @date 2018年5月12日 30 * 31 */ 32public class PrefixWildcardRegexpFuzzyQueryDemo { 33 34 public static void main(String[] args) throws IOException, ParseException { 35 // 使用的分词器 36 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 37 // 索引存储目录 38 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 39 // 索引读取器 40 IndexReader indexReader = DirectoryReader.open(directory); 41 // 索引搜索器 42 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 43 44 // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; 45 46 // PrefixQuery 前缀查询 47 PrefixQuery prefixQuery = new PrefixQuery(new Term("name", "think")); 48 System.out.println("********** PrefixQuery 前缀查询 ***********"); 49 doSearch(prefixQuery, indexSearcher); 50 51 // WildcardQuery 通配符查询 52 WildcardQuery wildcardQuery = new WildcardQuery( 53 new Term("name", "think*")); 54 55 System.out.println("********** WildcardQuery 通配符 ***********"); 56 doSearch(wildcardQuery, indexSearcher); 57 58 // WildcardQuery 通配符查询 59 WildcardQuery wildcardQuery2 = new WildcardQuery( 60 new Term("name", "厉害了???")); 61 System.out.println("********** WildcardQuery 通配符 ***********"); 62 doSearch(wildcardQuery2, indexSearcher); 63 64 // RegexpQuery 正则表达式查询 65 RegexpQuery regexpQuery = new RegexpQuery(new Term("name", "厉害.{4}")); 66 System.out.println("**********RegexpQuery 正则表达式查询***********"); 67 doSearch(regexpQuery, indexSearcher); 68 69 // FuzzyQuery 模糊查询 70 FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("name", "thind")); 71 System.out.println("**********FuzzyQuery 模糊查询***********"); 72 doSearch(fuzzyQuery, indexSearcher); 73 74 // FuzzyQuery 模糊查询 75 FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("name", "thinkd"), 2); 76 System.out.println("**********FuzzyQuery 模糊查询***********"); 77 doSearch(fuzzyQuery2, indexSearcher); 78 79 // FuzzyQuery 模糊查询 80 FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("name", "thinkpaddd")); 81 System.out.println("**********FuzzyQuery 模糊查询***********"); 82 doSearch(fuzzyQuery3, indexSearcher); 83 84 // FuzzyQuery 模糊查询 85 FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("name", "thinkdaddd")); 86 System.out.println("**********FuzzyQuery 模糊查询***********"); 87 doSearch(fuzzyQuery4, indexSearcher); 88 89 // 使用完毕,关闭、释放资源 90 indexReader.close(); 91 directory.close(); 92 } 93 94 private static void doSearch(Query query, IndexSearcher indexSearcher) 95 throws IOException { 96 // 打印输出查询 97 System.out.println("query: " + query.toString()); 98 99 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 100 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 101 102 System.out.println("**** 查询结果 "); 103 // 获得总命中数 104 System.out.println("总命中数:" + topDocs.totalHits); 105 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 106 for (ScoreDoc sdoc : topDocs.scoreDocs) { 107 // 根据文档id取存储的文档 108 Document hitDoc = indexSearcher.doc(sdoc.doc); 109 System.out.println("-------------- docId=" + sdoc.doc + ",score=" 110 + sdoc.score); 111 // 取文档的字段 112 System.out.println("prodId:" + hitDoc.get("prodId")); 113 System.out.println("name:" + hitDoc.get("name")); 114 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 115 System.out.println("price:" + hitDoc.get("price")); 116 117 System.out.println(); 118 } 119 120 } 121}
9、数值查询
前提:查询的数值字段必须索引。通过 IntPoint, LongPoint, FloatPoint, or DoublePoint 中的方法构建对应的查询。以IntPoint为例:

1// 精确值查询 2Query exactQuery = IntPoint.newExactQuery("price", 1999900); 3 4// 数值范围查询 5Query pointRangeQuery = IntPoint.newRangeQuery("price", 499900,1000000); 6 7// 集合查询 8Query setQuery = IntPoint.newSetQuery("price", 1999900, 1000000,2000000);
完整示例代码
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.document.IntPoint; 9import org.apache.lucene.index.DirectoryReader; 10import org.apache.lucene.index.IndexReader; 11import org.apache.lucene.queryparser.classic.ParseException; 12import org.apache.lucene.search.IndexSearcher; 13import org.apache.lucene.search.Query; 14import org.apache.lucene.search.ScoreDoc; 15import org.apache.lucene.search.TopDocs; 16import org.apache.lucene.store.Directory; 17import org.apache.lucene.store.FSDirectory; 18 19import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 20 21/** 22 * @Description: 数值查询 23 * @author liguangsheng 24 * @date 2018年5月12日 25 * 26 */ 27public class PointQueryDemo { 28 public static void main(String[] args) throws IOException, ParseException { 29 // 使用的分词器 30 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 31 // 索引存储目录 32 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 33 // 索引读取器 34 IndexReader indexReader = DirectoryReader.open(directory); 35 // 索引搜索器 36 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 37 38 // 精确值查询 39 Query exactQuery = IntPoint.newExactQuery("price", 1999900); 40 System.out.println("********** pointRangeQuery 数值精确查询 ***********"); 41 doSearch(exactQuery, indexSearcher); 42 43 // PointRangeQuery 数值范围查询 44 Query pointRangeQuery = IntPoint.newRangeQuery("price", 499900, 45 1000000); 46 System.out.println("********** pointRangeQuery 数值范围查询 ***********"); 47 doSearch(pointRangeQuery, indexSearcher); 48 49 // 集合查询 50 Query setQuery = IntPoint.newSetQuery("price", 1999900, 1000000, 51 2000000); 52 System.out.println("********** pointRangeQuery 数值集合查询 ***********"); 53 doSearch(setQuery, indexSearcher); 54 55 // 使用完毕,关闭、释放资源 56 indexReader.close(); 57 directory.close(); 58 } 59 60 private static void doSearch(Query query, IndexSearcher indexSearcher) 61 throws IOException { 62 // 打印输出查询 63 System.out.println("query: " + query.toString()); 64 65 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 66 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 67 68 System.out.println("**** 查询结果 "); 69 // 获得总命中数 70 System.out.println("总命中数:" + topDocs.totalHits); 71 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 72 for (ScoreDoc sdoc : topDocs.scoreDocs) { 73 // 根据文档id取存储的文档 74 Document hitDoc = indexSearcher.doc(sdoc.doc); 75 System.out.println("-------------- docId=" + sdoc.doc + ",score=" 76 + sdoc.score); 77 // 取文档的字段 78 System.out.println("prodId:" + hitDoc.get("prodId")); 79 System.out.println("name:" + hitDoc.get("name")); 80 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 81 System.out.println("price:" + hitDoc.get("price")); 82 83 System.out.println(); 84 } 85 86 } 87}
总结:不同的查询需求只是不同字段的不同基本查询的组合
三、QueryParser详解

QueryParser 查询解析生成器
Lucene QueryPaser包中提供了两类查询解析器:
A. 传统的解析器:QueryParser和MultiFieldQueryParser
B. 基于新的 flexible 框架的解析器:StandardQueryParser
1. 用法
用法1 传统解析器-单默认字段 QueryParser:
1QueryParser parser = new QueryParser("defaultFiled", analyzer); 2//parser.setPhraseSlop(2); 3Query query = parser.parse("query String");
用法2 传统解析器-多默认字段 MultiFieldQueryParser:
1// 传统查询解析器-多默认字段 2String[] multiDefaultFields = { "name", "type", "simpleIntro" }; 3MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( 4 multiDefaultFields, analyzer); 5// 设置默认的组合操作,默认是 OR 6multiFieldQueryParser.setDefaultOperator(Operator.OR); 7Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900");
用法3 新解析框架的标准解析器:StandardQueryParser:
1StandardQueryParser queryParserHelper = new StandardQueryParser(analyzer); 2// 设置默认字段 3// queryParserHelper.setMultiFields(CharSequence[] fields); 4// queryParserHelper.setPhraseSlop(8); 5// Query query = queryParserHelper.parse("a AND b", "defaultField"); 6Query query5 = queryParserHelper.parse( 7 "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900","name");
以上3种用法的完整示例代码:
1package com.study.lucene.searchdetail; 2 3import java.io.IOException; 4import java.nio.file.Paths; 5 6import org.apache.lucene.analysis.Analyzer; 7import org.apache.lucene.document.Document; 8import org.apache.lucene.document.IntPoint; 9import org.apache.lucene.index.DirectoryReader; 10import org.apache.lucene.index.IndexReader; 11import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; 12import org.apache.lucene.queryparser.classic.ParseException; 13import org.apache.lucene.queryparser.classic.QueryParser; 14import org.apache.lucene.queryparser.classic.QueryParser.Operator; 15import org.apache.lucene.queryparser.flexible.core.QueryNodeException; 16import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; 17import org.apache.lucene.search.BooleanClause.Occur; 18import org.apache.lucene.search.BooleanQuery; 19import org.apache.lucene.search.IndexSearcher; 20import org.apache.lucene.search.Query; 21import org.apache.lucene.search.ScoreDoc; 22import org.apache.lucene.search.TopDocs; 23import org.apache.lucene.store.Directory; 24import org.apache.lucene.store.FSDirectory; 25 26import com.study.lucene.ikanalyzer.Integrated.IKAnalyzer4Lucene7; 27 28 29/** 30 * @Description: QueryParser示例,查询解析器会对输入的查询短语进行分词 31 * @author liguangsheng 32 * @date 2018年5月12日 33 * 34 */ 35 36public class QueryParserDemo { 37 38 public static void main(String[] args) 39 throws IOException, ParseException, QueryNodeException { 40 // 使用的分词器 41 Analyzer analyzer = new IKAnalyzer4Lucene7(true); 42 // 索引存储目录 43 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); 44 // 索引读取器 45 IndexReader indexReader = DirectoryReader.open(directory); 46 // 索引搜索器 47 IndexSearcher indexSearcher = new IndexSearcher(indexReader); 48 49 /** 50 * 用法1 传统解析器-单默认字段 QueryParser: 51 */ 52 // 要搜索的默认字段 53 String defaultFiledName = "name"; 54 // 查询生成器(解析输入生成Query查询对象) 55 QueryParser parser = new QueryParser(defaultFiledName, analyzer); 56 // parser.setPhraseSlop(2); 57 // 通过parse解析输入,生成query对象 58 Query query1 = parser.parse( 59 "(name:\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); 60 // 等同query1,如果没有指明要搜索的字段则使用默认值name 61 Query query2 = parser.parse( 62 "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); 63 64 System.out.println("************** query1 ************"); 65 doSearch(query1, indexSearcher); 66 67 System.out.println("************** query2 ************"); 68 doSearch(query2, indexSearcher); 69 70 Query query3 = parser.parse( 71 "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:[800000 TO 1000000]"); 72 73 System.out.println("************** query3 ************"); 74 doSearch(query3, indexSearcher); 75 76 // 为什么query3查不出结果??? 该如何改 77 BooleanQuery bquery = new BooleanQuery.Builder() 78 .add(parser 79 .parse("(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 "), 80 Occur.MUST) 81 .add(IntPoint.newRangeQuery("price", 800000, 1000000), 82 Occur.MUST) 83 .build(); 84 85 System.out.println("************** bquery ************"); 86 doSearch(bquery, indexSearcher); 87 88 /** 89 * 用法2 传统解析器-多默认字段 MultiFieldQueryParser: 90 */ 91 String[] multiDefaultFields = { "name", "type", "simpleIntro" }; 92 MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( 93 multiDefaultFields, analyzer); 94 // 设置默认的操作 95 multiFieldQueryParser.setDefaultOperator(Operator.OR); 96 Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900"); 97 98 System.out.println("************** query4 ************"); 99 doSearch(query4, indexSearcher); 100 101 /** 102 * 用法3 新解析框架的标准解析器:StandardQueryParser: 103 */ 104 StandardQueryParser queryParserHelper = new StandardQueryParser( 105 analyzer); 106 // 设置默认字段 107 // queryParserHelper.setMultiFields(CharSequence[] fields); 108 // queryParserHelper.setPhraseSlop(8); 109 // Query query = queryParserHelper.parse("a AND b", "defaultField"); 110 Query query5 = queryParserHelper.parse( 111 "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900", 112 "name"); 113 114 System.out.println("************** query5 ************"); 115 doSearch(query5, indexSearcher); 116 117 // 使用完毕,关闭、释放资源 118 indexReader.close(); 119 directory.close(); 120 } 121 122 private static void doSearch(Query query, IndexSearcher indexSearcher) 123 throws IOException { 124 // 打印输出查询 125 System.out.println("query: " + query.toString()); 126 127 // 搜索,得到TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) 128 TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 129 130 System.out.println("**** 查询结果 "); 131 // 获得总命中数 132 System.out.println("总命中数:" + topDocs.totalHits); 133 // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 134 for (ScoreDoc sdoc : topDocs.scoreDocs) { 135 // 根据文档id取存储的文档 136 Document hitDoc = indexSearcher.doc(sdoc.doc); 137 System.out.println("-------------- docId=" + sdoc.doc + ",score=" 138 + sdoc.score); 139 // 取文档的字段 140 System.out.println("prodId:" + hitDoc.get("prodId")); 141 System.out.println("name:" + hitDoc.get("name")); 142 System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); 143 System.out.println("price:" + hitDoc.get("price")); 144 145 System.out.println(); 146 } 147 148 } 149}
2. 使用查询解析器前需考虑三点
1. 查询字符串应是由人输入的,而不应是你编程产生。如果你为了用查询解析器,而在你的应用中编程产生查询字符串,不可取,更应该直接使用基本查询API;
2. 未分词的字段,应直接使用基本查询API加入到查询中,而不应使用查询解析器;
3. 对于普通文本字段,使用查询解析器,而其他值字段:如 时间、数值,则应使用基本查询API
3. 查询描述规则语法(查询解析语法)
3.1 Term 词项:
单个词项的表示: 电脑
短语的表示: "联想笔记本电脑"
3.2 Field 字段:
字段名:
示例: name:“联想笔记本电脑” AND type:电脑
如果name是默认字段,则可写成: “联想笔记本电脑” AND type:电脑
如果查询串是:type:电脑 计算机 手机,只有第一个是type的值,后两个则是使用默认字段,翻译为type:动脑 OR name:计算机 OR name:手机
3.3 Term Modifiers 词项修饰符:
通配符:
? 单个字符
* 0个或多个字符
示例:te?t test* te*t
注意:通配符不可用在开头。
模糊查询,词后加 ~
示例: roam~
模糊查询最大支持两个不同字符。
示例: roam~1
正则表达式: /xxxx/
示例: /[mb]oat/
临近查询,短语后加 ~移动值
示例: "jakarta apache"~10
范围查询:
mod_date:[20020101 TO 20030101] 包含边界值
title:{Aida TO Carmen} 不包含边界值
词项加权:
使该词项的相关性更高,通过 ^数值来指定加权因子,默认加权因子值是1
示例:如要搜索包含 jakarta apache 的文章,jakarta更相关,则:
jakarta^4 apache
短语也可以: "jakarta apache"^4 "Apache Lucene"
3.4 Boolean 操作符
Lucene支持的布尔操作: AND, “+”, OR, NOT ,"-"
AND:
"jakarta apache" AND "Apache Lucene"
+ 必须包含:
+jakarta lucene
OR:
"jakarta apache" jakarta = "jakarta apache" OR jakarta
NOT 非:
"jakarta apache" NOT "Apache Lucene“
注意:NOT不可单项使用: NOT “Apache Lucene“ 是不对的
- 同NOT:
"jakarta apache" -"Apache Lucene“
3.5 组合 ()
字句组合:
(jakarta OR apache) AND website
字段组合:
title:(+return +"pink panther")
3.6 转义 \
对语法字符: + - && || ! ( ) { } [ ] ^ “ ~ * ? : \ / 进行转义。
如要查询包含 (1+1):2需要转义为\(1\+1\)\:2
源码获取地址: