搜索引擎(Lucene-搜索詳解)

Lucene搜索流程詳解

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

搜索核心API詳解

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包的功能。

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 

相關文章
相關標籤/搜索