搜索引擎(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 

相关文章
相关标签/搜索