Lucene搜尋引擎-搜尋
文章目錄
- 搜尋API詳解
- 基本查詢詳解
- TermQuery 詞項查詢
- BooleanQuery 布林查詢
- PhraseQuery 短語查詢
- MultiPhraseQuery 多重短語查詢
- SpanNearQuery 臨近查詢(跨度查詢)
- TermRangeQuery 詞項範圍查詢
- PrefixQuery, WildcardQuery, RegexpQuery
- FuzzyQuery 模糊查詢
- 數值查詢
- QuerParser查詢解析器
- 總結
如果對Lucene不熟悉的,請移步: 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:
搜尋API詳解
IndexReader 索引讀取器
Open一個讀取器,讀取的是該時刻點的索引檢視。如果後續索引發生改變,需重新open一個讀取器。
獲得索引讀取器的方式:
- DirectoryReader.open(IndexWriter indexWriter) 優先使用
- DirectoryReader.open(Directory)
- DirectoryReader.openIfChanged(DirectoryReader) 共享當前reader資源重新開啟一個(當索引變化時)
IndexReader分為兩類:
- 葉子讀取器:支援獲取stored fields, doc values, terms(詞項), and postings (詞項對應的文件)
- 複合讀取器:多個讀取器的複合,只可直接用它獲取stored fields 。在內部通過CompositeReader.getSequentialSubReaders 得到裡面的葉子讀取器來獲取其他資料
上述程式碼使用到的DirectoryReader 是 複合讀取器。
注意:IndexReader是執行緒安全的
IndexReader主要API:
LeafReader主要API:
IndexSearch 索引搜尋器
應用通過呼叫它的search(Query,int)過載方法在一個IndexReader上實現搜尋。出於效能的考慮,請使用一個IndexSearcher例項,除非索引發生變化。如索引更新了則通過DirectoryReader.openIfChanged(DirectoryReader) 取得新的讀取器,再建立新的搜尋器。
注意:IndexSearch是執行緒安全的
查詢結果
基本查詢詳解
常用的查詢API如下:
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)設定最大字句數。
// 布林查詢
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。有兩種方式來構建物件:
- 直接用構造方法
- 用裡面的Builder來構建
注意:
- Builder方式構造中的int 值為詞項的位置,後面加入的詞項的位置需>=前一詞項的位置
- 所有加入的詞項都匹配才算匹配(即使是你在同一位置加入多個詞項)
- 如果需要在同一位置匹配多個同義詞中的一個,適合用MultiPhraseQuery
短語查詢示例:
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();
移動因子slop說明:
slop是指兩個項的位置之間允許的最大間隔距離。
String name = “ThinkPad X1 Carbon 20KH0009CD/25CD 超極本輕薄膝上型電腦聯想”;
- 如果想用 thinkpad carbon 來匹配 name,則需要如何移動才會和name中的ThinkPand [X1] Carbon匹配呢?這種情況比較簡單,只要計算兩者間的距離,thinkpad向左移動1即可,此時slop=1
- 如果想用 *carbon thinkpad 來匹配 name,則只需要將carbon向右移動3位即可匹配ThinkPand [X1] Carbon,因此slop=3
MultiPhraseQuery 多重短語查詢
短語查詢的一種更通用的用法,支援同位置多個詞的OR匹配。通過裡面的Builder來構建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();
SpanNearQuery 臨近查詢(跨度查詢)
用於更復雜的短語查詢,可以指定詞間位置的最大間隔跨度。通過組合一系列的SpanQuery 例項來進行查詢,可以指定是否按順序匹配、slop、gap。
示例:
// 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();
TermRangeQuery 詞項範圍查詢
用於查詢包含某個範圍內的詞項的文件,如以字母開頭a到c的詞項。詞項在反向索引中是排序的,只需指定的開始詞項、結束詞項,就可以查詢該範圍的詞項。
如果是做數值的範圍查詢則用 PointRangeQuery
引數說明:
- field:欄位
- lowerTerm:下邊界詞
- upperTerm:上邊界詞
- includeLower:是否包含下邊界
- includeUpper:是否包含上邊界
示例:
// TermRangeQuery 詞項範圍查詢
TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name", "carbon", "張三", false, true);
PrefixQuery, WildcardQuery, RegexpQuery
- PrefixQuery 字首查詢
查詢包含以xxx為字首的詞項的文件,是萬用字元查詢,如 app,實際是 app* - WildcardQuery 萬用字元查詢
*表示0個或多個字元,?表示1個字元,\是轉義符。萬用字元查詢可能會比較慢,不可以萬用字元開頭(那樣就是所有詞項了) - 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"));
數值查詢
前提:查詢的數值欄位必須索引。
通過 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);
QuerParser查詢解析器
使用者的查詢需求是多變的,我們無法事先知道,也就無法事先編寫好構建查詢的程式碼。不同的查詢需求只是不同欄位的不同基本查詢的組合。
比如需求如下:
(name:“聯想膝上型電腦” OR simpleIntro :“聯想膝上型電腦”) AND type:電腦 AND price:[800000 TO 1000000]
使用者的查詢需求被很好的描述出來了,我們的搜尋程式中得能解讀這個描述,並把它轉為對應的查詢組合。這就是 QueryParser包的功能。
核心API:
Lucene QueryPaser包中提供了兩類查詢解析器:
- 傳統的解析器:QueryParser、MultiFieldQueryParser
- 基於新的 flexible 框架的解析器:StandardQueryParser
QueryParser 傳統解析器
單預設欄位
// 使用的分詞器
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");
MultiFieldQueryParser 傳統解析器
多預設欄位
// 傳統查詢解析器-多預設欄位
String[] multiDefaultFields = { "name", "type", "simpleIntro" };
MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(multiDefaultFields, analyzer);
// 設定預設的組合操作,預設是 OR
multiFieldQueryParser.setDefaultOperator(Operator.OR);
Query query4 = multiFieldQueryParser.parse("膝上型電腦 AND price:1999900");
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");
查詢解析語法
-
Term詞項
單個詞項的表示:電腦
短語的表示:“聯想膝上型電腦” -
Field欄位
示例1: name:“聯想膝上型電腦” AND type:電腦
如果name是預設欄位,則可寫成: “聯想膝上型電腦” AND type:電腦
示例2: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”
-
布林操作符
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
總結
- 查詢字串應是由人輸入的,而不應是你程式設計產生。如果你為了用查詢解析器,而在你的應用中程式設計產生查詢字串,不可取,更應該直接使用基本查詢API;
- 未分詞的欄位,應直接使用基本查詢API加入到查詢中,而不應使用查詢解析器;
- 對於普通文字欄位,使用查詢解析器,而其他值欄位:如 時間、數值,則應使用基本查詢API