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();
}
}
Lucene搜索核心API圖示
IndexReader 索引讀取器
open一個讀取器,讀取的是 該時該點的索引視圖。如果后續索引發生改變,需要重新open一個讀取器。
- 獲得索引讀取器的方式:
- DirectoryReader.open(IndexWriter indexWriter) 優先使用
- DirectoryReader.open(Directory)
- DirectoryReader.openIfChanged(DirectoryReader) 共享當前reader資源重新打開一個(當索引變化時)
-
IndexReader 分為兩類:
IndexReader 分為兩類
- 葉子讀取器:支持獲取stored fields(存儲字段),doc values,terms(詞項),and postings(詞項對應的文檔)
- 復合讀取器,多個讀取器的復合。只可直接用它獲取store fields(存儲字段)。在內部通過 CompositeReader.getSequentialSubReaders 得到里面的葉子讀取器來獲取其他數據。
- DirectoryReader 是 復合讀取器
注意:IndexReader 是線程安全的。
-
IndexReader 主要API:
IndexReader 主要API
IndexSearcher 索引搜索器
應用通過調用它的 search(Query,int)重載方法在一個 IndexReader 上實現搜索。出于性能的考慮,請使用一個 IndexSearcher 實例,除非索引發生變化。如索引更新了則通過 DirectoryReader.openIfChanged(DirectoryReader) 取得新的讀取器,在創建新的搜索器。
注意:IndexSearcher 是線程安全的
返回值參數的含義
-
TopDocs 搜索命中的結果集(Top-N)
TopDocs API -
TopFieldDocs 按字段排序的搜索命中結果集
TopFieldDocs API -
ScoreDoc
ScoreDoc API
Query 查詢詳解
1、TermQuery 詞項查詢
語法
TermQuery tq = new TermQuery(new Term("fieldName", "term"));
詞項查詢,最基本、最常用的查詢。用來查詢指定字段包含指定詞項的文檔。
#查詢字段為name 此項為 thinkpad的字段
TermQuery tq = new TermQuery(new Term(“name", “thinkpad"));
示例代碼:searchQueryDemo
2、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();
示例代碼:searchQueryDemo
3、PhraseQuery 短語查詢
最常用的查詢,匹配特點序列的多個詞項。PhraserQuery使用一個位置移動因子(slop)來決定任意兩個或多個詞項的位置可最大移動多少個位置來進行匹配,默認為0。
- 有兩種方式來構建對象:
注意:所有加入的詞項都匹配才算匹配(即使是你在同一位置加入多個詞項)。如果需要在同一位置匹配多個同義詞中的一個,適合用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();
示例代碼:searchQueryDemo
PhraseQuery slop 移動因子說明
String name = "ThinkPad X1 Carbon 20KH0009CD/25CD 超極本輕薄筆記本電腦聯想";
1、如果想用 “thinkpad carbon” 來匹配 name。因中間有 x1,則需要將thinkpad 向右移動1個位置。
2、如果想用 “carbon thinkpad” 來匹配 name。因中間有 x1,則需要將carbon 向右移動3個位置。
slop 移動因子 表示最大可以移動的位置 所以說上面第二個情況 slop也可以為4,只要大于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", "聯想","筆記本電腦");
示例代碼:PhraseQuerySlopDemo
可參考:https://blog.csdn.net/rick_123/article/details/6708527
4、MultiPhraseQuery 多重短語查詢
短語查詢的一種更通用的用法,支持同位置多個詞的OR匹配。通過里面的 Builder 來構建 MultiPhraseQuery。
示例:
// 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();
// 對比 PhraseQuery在同位置加入多個詞 ,同位置的多個詞都需匹配,所以查不出。
PhraseQuery pquery = new PhraseQuery.Builder().add(terms[0], 0)
.add(terms[1], 0).add(t, 1).build();
示例代碼:searchQueryDemo
5、SpanNearQuery 臨近查詢(跨度查詢)
用于更復雜的短語查詢,可以指定詞間位置的最大間隔跨度。通過組合一系列的 SpanQuery 實例來進行查詢,可以指定是否按順訊匹配、slop(移動因子)、gap(最大的間隔跨度)。
- newOrderedNearQuery(String) 排序的builder
- newUnorderedNearQuery(String) 無排序的builder
示例:
// 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
6、TermRangeQuery 詞項范圍查詢
用于查詢包含某個范圍內的詞項的文檔,如以字母開頭a到c的詞項。詞項在反向索引中是排序的,只需要指定的開始詞項、結束詞項,皆可以查詢該范圍的詞項。如果是做數值的范圍查詢則用 PointRangeQuery。
示例:
// TermRangeQuery 詞項范圍查詢
TermRangeQuery termRangeQuery = TermRangeQuery.newStringRange("name",
"carbon", "張三", false, true);
完整示例代碼:TermRangeQueryDemo
7、PerfixQuery,WildcardQuery,RegexpQuery
- PerfixQuery 前綴查詢
查詢包含以 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}"));
完整示例代碼:PrefixWildcardRegexpFuzzyQueryDemo
8、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
9、數值查詢
前提:查詢的數值字段必須索引。通過 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
QueryParser詳解
為什么需要查詢解析生成器?
用戶的查詢需求是多變的,我們無法事先知道,也就無法事先編寫好構建查詢的代碼。
因為不同的查詢需求只是不同字段的不同基本查詢的組合。
所以我們就可以這么寫:
(name:"聯想筆記本電腦" OR simpleIntro :"聯想筆記本電腦") AND type:電腦 AND price:[800000 TO 1000000]
QueryParser 查詢解析生成器
- Lucene QueryPaser包中提供了兩類查詢解析器:
- 傳統的解析器
- QueryParser
- MultiFieldQueryParser
- 基于新的 flexible 框架的解析器
- StandardQuertParser
兩種解析框架,一套查詢描述規則。
用法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");
完整示例代碼:QueryParserDemo
用法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");
完整示例代碼:QueryParserDemo
用法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
使用查詢解析器前需考慮三點:
- 查詢字符串應是由人輸入的,而不是你編程產生的。如果你為了用查詢解析器,而在你的應用中編程產生查詢字符串,不可取,更應該直接使用基本查詢API。
- 未分詞的字段,應直接使用基本查詢API加入到查詢中,而不應該使用查詢解析器。
- 對于普通文本字段,使用查詢解析器,而其他字段:如 時間、數值,則應使用基本查詢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
- NOT 非
"jakarta apache" NOT "Apache Lucene“
#注意:NOT不可單項使用:
NOT “Apache Lucene“ #不可
- AND
"jakarta apache" AND "Apache Lucene"
- - 同NOT
"jakarta apache" -"Apache Lucene"
- + 必須包含
+jakarta lucene
- 組合 ()
- 字句組合
(jakarta OR apache) AND website
- 字段組合
title:(+return +"pink panther")
- 轉義 \
對語法字符:+ - && || ! ( ) { } [ ] ^ “ ~ * ? : \ / 進行轉義。
#如要查詢包含 (1+1):2
\(1\+1\)\:2