問題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