问题1:搜索的过程是怎样的?java
回顾架构图web
Lucene搜索代码示例正则表达式
public class SearchBaseFlow { public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 要搜索的字段 String filedName = "name"; // 查询生成器(解析输入生成Query查询对象) QueryParser parser = new QueryParser(filedName, analyzer); // 经过parse解析输入(分词),生成query对象 Query query = parser.parse("Thinkpad"); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); //前10条 //得到总命中数 System.out.println(topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); // 取文档的字段 System.out.println(hitDoc.get(filedName)); } // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } }
核心API图示:apache
IndexReader 索引读取器编程
Open一个读取器,读取的是该时刻点的索引视图。若是后续索引起生改变,需从新open一个读取器。得到索引读取器的方式:安全
DirectoryReader.open(IndexWriter indexWriter) 优先使用 架构
DirectoryReader.open(Directory)app
DirectoryReader.openIfChanged(DirectoryReader) 共享当前reader资源从新打开一个(当索引变化时)框架
IndexReader分为两类:post
叶子读取器:支持获取stored fields, doc values, terms(词项), and postings (词项对应的文档)
复合读取器:多个读取器的复合。只可直接用它获取stored fields 。在内部经过CompositeReader.getSequentialSubReaders 获得里面的叶子读取器来获取其余数据。
DirectoryReader 是 复合读取器
注意:IndexReader是线程安全的。
IndexReader 主要API:
IndexSearcher 索引搜索器
应用经过调用它的search(Query,int)重载方法在一个IndexReader上实现搜索。出于性能的考虑,请使用一个IndexSearcher实例,除非索引起生变化。如索引更新了则经过DirectoryReader.openIfChanged(DirectoryReader) 取得新的读取器,再建立新的搜索器。
注意:IndexSearcher是线程安全的。
IndexSearcher 索引搜索器 API
Query 查询的表示。 它的可实例化子类有:
它们是lucene中的基本查询。咱们能够用它们来建立查询。
要掌握的基本查询
一、TermQuery 词项查询
TermQuery tq = new TermQuery(new Term("fieldName", "term"));
词项查询,最基本、最经常使用的查询。用来查询指定字段包含指定词项的文档
TermQuery tq = new TermQuery(new Term(“name", “thinkpad"));
二、BooleanQuery 布尔查询
搜索的条件每每是多个的,如要查询名称包含“电脑” 或 “thinkpad”的商品,就须要两个词项查询作或合并。布尔查询就是用来组合多个子查询的。每一个子查询称为布尔字句 BooleanClause,布尔字句自身也能够是组合的。 组合关系支持以下四种:
Occur.SHOULD 或
Occur.MUST 且
Occur.MUST_NOT 且非
Occur.FILTER 同 MUST,但该字句不参与评分
布尔查询默认的最大字句数为1024,在将通配符查询这样的查询rewriter为布尔查询时,每每会产生不少的字句,可能抛出TooManyClauses 异常。可经过BooleanQuery.setMaxClauseCount(int)设置最大字句数。
BooleanQuery 布尔查询示例
// 布尔查询 Query query1 = new TermQuery(new Term(filedName, "thinkpad")); Query query2 = new TermQuery(new Term("simpleIntro", "英特尔")); BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder(); booleanQueryBuilder.add(query1, Occur.SHOULD); booleanQueryBuilder.add(query2, Occur.MUST); BooleanQuery booleanQuery = booleanQueryBuilder.build(); // 可像下一行这样写 // BooleanQuery booleanQuery = new BooleanQuery.Builder() // .add(query1, Occur.SHOULD).add(query2, Occur.MUST).build();
三、PhraseQuery 短语查询
最经常使用的查询,匹配特色序列的多个词项。PhraserQuery使用一个位置移动因子(slop)来决定任意两个词项的位置可最大移动多少个位置来进行匹配,默认为0。有两种方式来构建对象:
PhraseQuery 短语查询示例
PhraseQuery phraseQuery1 = new PhraseQuery("name", "thinkpad", "carbon"); PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad", "carbon"); PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); PhraseQuery phraseQuery4 = new PhraseQuery.Builder() .add(new Term("name", "笔记本电脑"), 4) .add(new Term("name", "联想"), 5).build(); // 这两句等同 PhraseQuery phraseQuery5 = new PhraseQuery.Builder() .add(new Term("name", "笔记本电脑"), 0) .add(new Term("name", "联想"), 1).build();
PhraseQuery slop 移动因子说明
String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想";
一、若是想用 “thinkpad carbon” 来匹配 name。因中间有 x1,则须要将thinkpad 向右移动1个位置。
二、若是想用 “carbon thinkpad” 来匹配 name。因中间有 x1,则须要将carbon 向右移动3个位置。
// String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; // PhraseQuery 短语查询 PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad","carbon"); // slop示例 PhraseQuery phraseQuery2Slop = new PhraseQuery(3, "name", "carbon", "thinkpad"); PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); // slop示例 PhraseQuery phraseQuery3Slop = new PhraseQuery(2, "name", "联想","笔记本电脑");
QueryParserDemo
public class QueryParserDemo { /** * lucene QueryParser示例 * * @throws QueryNodeException */ public static void main(String[] args) throws IOException, ParseException, QueryNodeException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 要搜索的默认字段 String defaultFiledName = "name"; // 查询生成器(解析输入生成Query查询对象) QueryParser parser = new QueryParser(defaultFiledName, analyzer); // parser.setPhraseSlop(2); // 经过parse解析输入,生成query对象 Query query1 = parser.parse( "(name:\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); // 等同query1 Query query2 = parser.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); System.out.println("************** query1 ************"); doSearch(query1, indexSearcher); System.out.println("************** query2 ************"); doSearch(query2, indexSearcher); Query query3 = parser.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:[800000 TO 1000000]"); System.out.println("************** query3 ************"); doSearch(query3, indexSearcher); // 为何query3查不出结果??? 该如何改 BooleanQuery bquery = new BooleanQuery.Builder() .add(parser .parse("(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 "), Occur.MUST) .add(IntPoint.newRangeQuery("price", 800000, 1000000), Occur.MUST) .build(); System.out.println("************** bquery ************"); doSearch(bquery, indexSearcher); // 传统查询解析器-多默认字段 String[] multiDefaultFields = { "name", "type", "simpleIntro" }; MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( multiDefaultFields, analyzer); // 设置默认的操做 multiFieldQueryParser.setDefaultOperator(Operator.OR); Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900"); System.out.println("************** query4 ************"); doSearch(query4, indexSearcher); StandardQueryParser queryParserHelper = new StandardQueryParser( analyzer); // 设置默认字段 // queryParserHelper.setMultiFields(CharSequence[] fields); // queryParserHelper.setPhraseSlop(8); // Query query = queryParserHelper.parse("a AND b", "defaultField"); Query query5 = queryParserHelper.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900", "name"); System.out.println("************** query5 ************"); doSearch(query5, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
四、MultiPhraseQuery 多重短语查询
短语查询的一种更通用的用法,支持同位置多个词的OR匹配。经过里面的Builder来构建MultiPhraseQuery:
MultiPhraseQuery 多重短语查询 Term[] terms = new Term[2]; terms[0] = new Term("name", "笔记本"); terms[1] = new Term("name", "笔记本电脑"); Term t = new Term("name", "联想"); MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder() .add(terms).add(t).build(); // 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,因此查不出。 PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0) .add(terms[1], 0).add(t, 1).build();
SearchQueryDemo
public class SearchQueryDemo { /** * lucene 搜索查询示例 */ public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 要搜索的字段 String filedName = "name"; // 一、词项查询 Query query1 = new TermQuery(new Term(filedName, "thinkpad")); System.out.println("************** 词项查询 ******************"); doSearch(query1, indexSearcher); // 二、布尔查询 Query query2 = new TermQuery(new Term("simpleIntro", "英特尔")); BooleanQuery.Builder booleanQueryBuilder = new BooleanQuery.Builder(); booleanQueryBuilder.add(query1, Occur.SHOULD); booleanQueryBuilder.add(query2, Occur.MUST); BooleanQuery booleanQuery = booleanQueryBuilder.build(); // 可像下一行这样写 // BooleanQuery booleanQuery = new BooleanQuery.Builder() // .add(query1, Occur.SHOULD).add(query2, Occur.MUST).build(); System.out.println("************** 布尔查询 ******************"); doSearch(booleanQuery, indexSearcher); // 三、PhraseQuery 短语查询 // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; PhraseQuery phraseQuery1 = new PhraseQuery("name", "thinkpad", "carbon"); System.out.println("************** phrase 短语查询 ******************"); doSearch(phraseQuery1, indexSearcher); PhraseQuery phraseQuery2 = new PhraseQuery(1, "name", "thinkpad", "carbon"); System.out.println("************** phrase 短语查询 ******************"); doSearch(phraseQuery2, indexSearcher); // slop示例 PhraseQuery phraseQuery2Slop = new PhraseQuery(3, "name", "carbon", "thinkpad"); System.out.println("********** phrase slop 短语查询 ***************"); doSearch(phraseQuery2Slop, indexSearcher); PhraseQuery phraseQuery3 = new PhraseQuery("name", "笔记本电脑", "联想"); System.out.println("************** phrase 短语查询 ******************"); doSearch(phraseQuery3, indexSearcher); // slop示例 PhraseQuery phraseQuery3Slop = new PhraseQuery(2, "name", "联想", "笔记本电脑"); System.out.println("************** phrase s 短语查询 ******************"); doSearch(phraseQuery3Slop, indexSearcher); PhraseQuery phraseQuery4 = new PhraseQuery.Builder() .add(new Term("name", "笔记本电脑"), 4) // 四、5是这个词的位置,和 0、1等同 .add(new Term("name", "联想"), 5).build(); System.out.println("********** phrase Builder 1 短语查询 **************"); doSearch(phraseQuery4, indexSearcher); // 等同 phraseQuery4 PhraseQuery phraseQuery5 = new PhraseQuery.Builder() .add(new Term("name", "笔记本电脑"), 0) // 四、5是这个词的位置,和 0、1等同 .add(new Term("name", "联想"), 1).build(); System.out.println("*********** phrase Builder 2 短语查询 ***********"); doSearch(phraseQuery5, indexSearcher); // 4 MultiPhraseQuery 多重短语查询 Term[] terms = new Term[2]; terms[0] = new Term("name", "笔记本"); terms[1] = new Term("name", "笔记本电脑"); Term t = new Term("name", "联想"); MultiPhraseQuery multiPhraseQuery = new MultiPhraseQuery.Builder() .add(terms).add(t).build(); System.out.println( "************** multiPhraseQuery 短语查询 ******************"); doSearch(multiPhraseQuery, indexSearcher); // 对比 PhraseQuery在同位置加入多个词 ,同位置的多个词都需匹配,因此查不出。 PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0) .add(terms[1], 0).add(t, 1).build(); System.out.println( "************** multiPhraseQuery 对比 PhraseQuery 短语查询 ******************"); doSearch(pquery, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
五、SpanNearQuery 临近查询(跨度查询)
用于更复杂的短语查询,能够指定词间位置的最大间隔跨度。经过组合一系列的SpanQuery 实例来进行查询,能够指定是否按顺序匹配、slop、gap。
SpanNearQuery 临近查询示例
// SpanNearQuery 临近查询 SpanTermQuery tq1 = new SpanTermQuery(new Term("name", "thinkpad")); SpanTermQuery tq2 = new SpanTermQuery(new Term("name", "carbon")); SpanNearQuery spanNearQuery = new SpanNearQuery( new SpanQuery[] { tq1, tq2 }, 1, true); // SpanNearQuery 临近查询 gap slop 使用 SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery .newOrderedNearQuery("name"); spanNearQueryBuilder.addClause(tq1).addGap(0).setSlop(1) .addClause(tq2); SpanNearQuery spanNearQuery5 = spanNearQueryBuilder.build();
SpanNearQueryDemo
public class SpanNearQueryDemo { /** * lucene 搜索查询示例 */ public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; // SpanNearQuery 临近查询 SpanTermQuery tq1 = new SpanTermQuery(new Term("name", "thinkpad")); SpanTermQuery tq2 = new SpanTermQuery(new Term("name", "carbon")); SpanNearQuery spanNearQuery = new SpanNearQuery( new SpanQuery[] { tq1, tq2 }, 1, true); System.out.println("************** SpanNearQuery 临近查询 ************"); doSearch(spanNearQuery, indexSearcher); // 下面的例子词是反序的 SpanNearQuery spanNearQuery2 = new SpanNearQuery( new SpanQuery[] { tq2, tq1 }, 1, true); System.out.println( "************** SpanNearQuery 临近查询 2 1,true************"); doSearch(spanNearQuery2, indexSearcher); SpanNearQuery spanNearQuery3 = new SpanNearQuery( new SpanQuery[] { tq2, tq1 }, 3, true); System.out.println( "************** SpanNearQuery 临近查询 3 3, true************"); doSearch(spanNearQuery3, indexSearcher); SpanNearQuery spanNearQuery4 = new SpanNearQuery( new SpanQuery[] { tq2, tq1 }, 3, false); System.out.println( "************** SpanNearQuery 临近查询 4 3, false************"); doSearch(spanNearQuery4, indexSearcher); // SpanNearQuery 临近查询 gap slop 使用 1 SpanTermQuery ctq1 = new SpanTermQuery(new Term("name", "张三")); SpanTermQuery ctq2 = new SpanTermQuery(new Term("name", "在理")); SpanNearQuery.Builder spanNearQueryBuilder = SpanNearQuery .newOrderedNearQuery("name"); spanNearQueryBuilder.addClause(ctq1).addGap(0).setSlop(2) .addClause(ctq2); System.out.println("************** SpanNearQuery 临近查询 ************"); doSearch(spanNearQueryBuilder.build(), indexSearcher); // SpanNearQuery 临近查询 gap slop 使用 2 SpanNearQuery.Builder spanNearQueryBuilder2 = SpanNearQuery .newOrderedNearQuery("name"); spanNearQueryBuilder2.addClause(ctq1).addGap(2).setSlop(0) .addClause(ctq2); System.out.println("************** SpanNearQuery 临近查询 ************"); doSearch(spanNearQueryBuilder2.build(), indexSearcher); // SpanNearQuery 临近查询 gap slop 使用 3 SpanNearQuery.Builder spanNearQueryBuilder3 = SpanNearQuery .newOrderedNearQuery("name"); spanNearQueryBuilder3.addClause(ctq1).addGap(1).setSlop(1) .addClause(ctq2); System.out.println("************** SpanNearQuery 临近查询 ************"); doSearch(spanNearQueryBuilder3.build(), indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
六、TermRangeQuery 词项范围查询
用于查询包含某个范围内的词项的文档,如以字母开头a到c的词项。词项在反向索引中是排序的,只需指定的开始词项、结束词项,就能够查询该范围的词项。 若是是作数值的范围查询则用 PointRangeQuery 。
TermRangeQuery 词项范围查询示例
// TermRangeQuery 词项范围查询 TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name", "carbon", "张三", false, true);
TermRangeQueryDemo
public class TermRangeQueryDemo { /** * lucene 搜索查询示例 */ public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; // TermRangeQuery 词项范围查询 TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name", "carbon", "张三", false, true); System.out.println("********** TermRangeQuery 词项范围查询 ***********"); doSearch(termRangeQuery, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
七、PrefixQuery, WildcardQuery, RegexpQuery
PrefixQuery 前缀查询 查询包含以xxx为前缀的词项的文档,是通配符查询,如 app,实际是 app*
WildcardQuery 通配符查询 *表示0个或多个字符,?表示1个字符,\是转义符。通配符查询可能会比较慢,不能够通配符开头(那样就是全部词项了)
RegexpQuery 正则表达式查询 词项符合某正则表达式
注意:这三种查询可能会比较慢,使用时+谨慎
PrefixQuery, WildcardQuery, RegexpQuery 示例
// PrefixQuery 前缀查询 PrefixQuery prefixQuery = new PrefixQuery(new Term("name", "think")); // WildcardQuery 通配符查询 WildcardQuery wildcardQuery = new WildcardQuery( new Term("name", "think*")); // WildcardQuery 通配符查询 WildcardQuery wildcardQuery2 = new WildcardQuery( new Term("name", "厉害了???")); // RegexpQuery 正则表达式查询 RegexpQuery regexpQuery = new RegexpQuery(new Term("name", "厉害.{4}"));
八、FuzzyQuery 模糊查询
简单地与索引词项进行相近匹配,容许最大2个不一样字符。经常使用于拼写错误的容错:如把 “thinkpad” 拼成 “thinkppd”或 “thinkd”,使用FuzzyQuery 仍可搜索到正确的结果。
// FuzzyQuery 模糊查询 FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("name", "thind")); FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("name", "thinkd"), 2); FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("name", "thinkpaddd")); FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("name", "thinkdaddd"));
PrefixWildcardRegexpFuzzyQueryDemo
public class PrefixWildcardRegexpFuzzyQueryDemo { /** * lucene 搜索查询示例 */ public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超极本轻薄笔记本电脑联想"; // PrefixQuery 前缀查询 PrefixQuery prefixQuery = new PrefixQuery(new Term("name", "think")); System.out.println("********** PrefixQuery 前缀查询 ***********"); doSearch(prefixQuery, indexSearcher); // WildcardQuery 通配符查询 WildcardQuery wildcardQuery = new WildcardQuery( new Term("name", "think*")); System.out.println("********** WildcardQuery 通配符 ***********"); doSearch(wildcardQuery, indexSearcher); // WildcardQuery 通配符查询 WildcardQuery wildcardQuery2 = new WildcardQuery( new Term("name", "厉害了???")); System.out.println("********** WildcardQuery 通配符 ***********"); doSearch(wildcardQuery2, indexSearcher); // RegexpQuery 正则表达式查询 RegexpQuery regexpQuery = new RegexpQuery(new Term("name", "厉害.{4}")); System.out.println("**********RegexpQuery 正则表达式查询***********"); doSearch(regexpQuery, indexSearcher); // FuzzyQuery 模糊查询 FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("name", "thind")); System.out.println("**********FuzzyQuery 模糊查询***********"); doSearch(fuzzyQuery, indexSearcher); // FuzzyQuery 模糊查询 FuzzyQuery fuzzyQuery2 = new FuzzyQuery(new Term("name", "thinkd"), 2); System.out.println("**********FuzzyQuery 模糊查询***********"); doSearch(fuzzyQuery2, indexSearcher); // FuzzyQuery 模糊查询 FuzzyQuery fuzzyQuery3 = new FuzzyQuery(new Term("name", "thinkpaddd")); System.out.println("**********FuzzyQuery 模糊查询***********"); doSearch(fuzzyQuery3, indexSearcher); // FuzzyQuery 模糊查询 FuzzyQuery fuzzyQuery4 = new FuzzyQuery(new Term("name", "thinkdaddd")); System.out.println("**********FuzzyQuery 模糊查询***********"); doSearch(fuzzyQuery4, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
九、数值查询
前提:查询的数值字段必须索引。经过 IntPoint, LongPoint, FloatPoint, or DoublePoint 中的方法构建对应的查询。以IntPoint为例:
数值查询示例
// 精确值查询 Query exactQuery = IntPoint.newExactQuery("price", 1999900); // 数值范围查询 Query pointRangeQuery = IntPoint.newRangeQuery("price", 499900,1000000); // 集合查询 Query setQuery = IntPoint.newSetQuery("price", 1999900, 1000000,2000000);
PointQueryDemo
public class PointQueryDemo { /** * lucene 搜索查询示例 */ public static void main(String[] args) throws IOException, ParseException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 精确值查询 Query exactQuery = IntPoint.newExactQuery("price", 1999900); System.out.println("********** pointRangeQuery 数值精确查询 ***********"); doSearch(exactQuery, indexSearcher); // PointRangeQuery 数值范围查询 Query pointRangeQuery = IntPoint.newRangeQuery("price", 499900, 1000000); System.out.println("********** pointRangeQuery 数值范围查询 ***********"); doSearch(pointRangeQuery, indexSearcher); // 集合查询 Query setQuery = IntPoint.newSetQuery("price", 1999900, 1000000, 2000000); System.out.println("********** pointRangeQuery 数值集合查询 ***********"); doSearch(setQuery, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
查询小结
问1:若是用户输入了“联想笔记本电脑”来搜索商品名称或商品简介中包含这个短语的商品,这个查询咱们该如何构建?
查询小结-练习
问题1:若是用户输入了“联想笔记本电脑”来搜索商品名称或商品简介中包含这个短语的商品,这个查询咱们该如何构建?
一、分别在商品名、简介字段上构建短语查询
二、将两个短语查询做OR组合成布尔查询
问题2:用户的查询需求变了:如要查询:名称或简介中包含“联想笔记本电脑”, 且类别是“电脑”,且价格8000-100000元的商品,该如何构建查询?
一、分别在商品名、简介字段上构建短语查询
二、将两个短语查询做OR组合成布尔查询
三、在类别字段上创建词项查询,再与前面的布尔查询作AND组合
四、在价格字段上创建数值范围查询,再与前面的作AND组合
查询小结-拓展
问题3:若是用户查询需求又变了,你怎么办? 又变了、又变了、又变了…. 你怎么办?
结论:用户的查询需求是多变的,咱们没法事先知道,也就没法事先编写好构建查询的代码。
思考1:不能事先知道用户的查询需求,能不能要用户在输入时,不光输入查询值,把他的查询需求也一并输入?
思考2:若是要这样作,是否是得在咱们和用户之间创建一套查询需求的描述规则?
思考3:这套规则应该是怎样的?回顾问题一、问题2的答案,请看他们有什么想通的地方没?
结论:不一样的查询需求只是不一样字段的不一样基本查询的组合。
查询需求描述规则可不能够像下面这样:
(name:"联想笔记本电脑" OR simpleIntro :"联想笔记本电脑") AND type:电脑 AND price:[800000 TO 1000000]
用户的查询需求被很好的描述出来了,咱们的搜索程序中得能解读这个描述,并把它转为对应的查询组合。这就是 QueryParser包的功能。
核心API图示:
QueryParser 查询解析生成器
Lucene QueryPaser包中提供了两类查询解析器:
1.传统的解析器
QueryParser MultiFieldQueryParser
2.基于新的 flexible 框架的解析器
StandardQueryParser
两种解析框架,一套查询描述规则。
用法1 传统解析器-单默认字段 QueryParser:
QueryParser parser = new QueryParser("defaultFiled", analyzer); //parser.setPhraseSlop(2); Query query = parser.parse("query String");
示例:
// 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 要搜索的默认字段 String defaultFiledName = "name"; // 查询生成器(解析输入生成Query查询对象) QueryParser parser = new QueryParser(defaultFiledName, analyzer); // 经过parse解析输入,生成query对象 Query query1 = parser.parse( "(name:\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900");
用法2 传统解析器-多默认字段 MultiFieldQueryParser:
// 传统查询解析器-多默认字段 String[] multiDefaultFields = { "name", "type", "simpleIntro" }; MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( multiDefaultFields, analyzer); // 设置默认的组合操做,默认是 OR multiFieldQueryParser.setDefaultOperator(Operator.OR); Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900");
用法3 新解析框架的标准解析器:StandardQueryParser:
StandardQueryParser queryParserHelper = new StandardQueryParser(analyzer); // 设置默认字段 // queryParserHelper.setMultiFields(CharSequence[] fields); // queryParserHelper.setPhraseSlop(8); // Query query = queryParserHelper.parse("a AND b", "defaultField"); Query query5 = queryParserHelper.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900","name");
QueryParserDemo
public class QueryParserDemo { /** * lucene QueryParser示例 * * @throws QueryNodeException */ public static void main(String[] args) throws IOException, ParseException, QueryNodeException { // 使用的分词器 Analyzer analyzer = new IKAnalyzer4Lucene7(true); // 索引存储目录 Directory directory = FSDirectory.open(Paths.get("f:/test/indextest")); // 索引读取器 IndexReader indexReader = DirectoryReader.open(directory); // 索引搜索器 IndexSearcher indexSearcher = new IndexSearcher(indexReader); // 要搜索的默认字段 String defaultFiledName = "name"; // 查询生成器(解析输入生成Query查询对象) QueryParser parser = new QueryParser(defaultFiledName, analyzer); // parser.setPhraseSlop(2); // 经过parse解析输入,生成query对象 Query query1 = parser.parse( "(name:\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); // 等同query1 Query query2 = parser.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:999900"); System.out.println("************** query1 ************"); doSearch(query1, indexSearcher); System.out.println("************** query2 ************"); doSearch(query2, indexSearcher); Query query3 = parser.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:[800000 TO 1000000]"); System.out.println("************** query3 ************"); doSearch(query3, indexSearcher); // 为何query3查不出结果??? 该如何改 BooleanQuery bquery = new BooleanQuery.Builder() .add(parser .parse("(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 "), Occur.MUST) .add(IntPoint.newRangeQuery("price", 800000, 1000000), Occur.MUST) .build(); System.out.println("************** bquery ************"); doSearch(bquery, indexSearcher); // 传统查询解析器-多默认字段 String[] multiDefaultFields = { "name", "type", "simpleIntro" }; MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser( multiDefaultFields, analyzer); // 设置默认的操做 multiFieldQueryParser.setDefaultOperator(Operator.OR); Query query4 = multiFieldQueryParser.parse("笔记本电脑 AND price:1999900"); System.out.println("************** query4 ************"); doSearch(query4, indexSearcher); StandardQueryParser queryParserHelper = new StandardQueryParser( analyzer); // 设置默认字段 // queryParserHelper.setMultiFields(CharSequence[] fields); // queryParserHelper.setPhraseSlop(8); // Query query = queryParserHelper.parse("a AND b", "defaultField"); Query query5 = queryParserHelper.parse( "(\"联想笔记本电脑\" OR simpleIntro:英特尔) AND type:电脑 AND price:1999900", "name"); System.out.println("************** query5 ************"); doSearch(query5, indexSearcher); // 使用完毕,关闭、释放资源 indexReader.close(); directory.close(); } private static void doSearch(Query query, IndexSearcher indexSearcher) throws IOException { // 打印输出查询 System.out.println("query: " + query.toString()); // 搜索,获得TopN的结果(结果中有命中总数,topN的scoreDocs(评分文档(文档id,评分))) TopDocs topDocs = indexSearcher.search(query, 10); // 前10条 System.out.println("**** 查询结果 "); // 得到总命中数 System.out.println("总命中数:" + topDocs.totalHits); // 遍历topN结果的scoreDocs,取出文档id对应的文档信息 for (ScoreDoc sdoc : topDocs.scoreDocs) { // 根据文档id取存储的文档 Document hitDoc = indexSearcher.doc(sdoc.doc); System.out.println("-------------- docId=" + sdoc.doc + ",score=" + sdoc.score); // 取文档的字段 System.out.println("prodId:" + hitDoc.get("prodId")); System.out.println("name:" + hitDoc.get("name")); System.out.println("simpleIntro:" + hitDoc.get("simpleIntro")); System.out.println("price:" + hitDoc.get("price")); System.out.println(); } } }
使用查询解析器前需考虑三点:
1.查询字符串应是由人输入的,而不该是你编程产生。若是你为了用查询解析器,而在你的应用中编程产生查询字符串,不可取,更应该直接使用基本查询API;
2.未分词的字段,应直接使用基本查询API加入到查询中,而不该使用查询解析器;
3.对于普通文本字段,使用查询解析器,而其余值字段:如 时间、数值,则应使用基本查询API
查询描述规则语法(查询解析语法):
Term 词项:
单个词项的表示: 电脑
短语的表示: "联想笔记本电脑"
Field 字段:
字段名:
示例: name:“联想笔记本电脑” AND type:电脑
若是name是默认字段,则可写成: “联想笔记本电脑” AND type:电脑
若是查询串是:type:电脑 计算机 手机
注意:只有第一个是type的值,后两个则是使用默认字段。
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
Boolean 操做符:
Lucene支持的布尔操做: AND, “+”, OR, NOT ,"-"
OR:"jakarta apache" jakarta = "jakarta apache" OR jakarta
AND:"jakarta apache" AND "Apache Lucene"
+ 必须包含: +jakarta lucene
NOT 非:"jakarta apache" NOT "Apache Lucene“ 注意:NOT不可单项使用: NOT “Apache Lucene“ 不能够
- 同NOT:"jakarta apache" -"Apache Lucene“
组合 ()
字句组合 (jakarta OR apache) AND website
字段组合 title:(+return +"pink panther")
转义 \
对语法字符: + - && || ! ( ) { } [ ] ^ “ ~ * ? : \ / 进行转义。
如要查询包含 (1+1):2 \(1\+1\)\:2