lucnen核心之查询

本文详细介绍了Lucene的查询机制,包括基础查询如TermQuery、BooleanQuery和短语查询,以及高级查询如RangeQuery和FuzzyQuery。文章还探讨了Lucene的打分机制,解释了如何通过Similarity和查询策略改变得分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

lucnen核心之查询

一、基础查询 Search Basics

  lucene提供了很多的Query查询,这些查询大部分在org.apache.lucene.search这个包下,一部分在spans和queries模块下,这些实现可以进行多种组合,以此来实现复杂的查询功能。在下面第二部分查询类中,着重说明了几个更重要的查询类。在自定义查询部分,有更多的关于自定义你自己的查询类的说明。

  执行查询时,我们需要调用的IndexSearcher的search方法:

  IndexSearcher.search(Query,int)

  一旦一个查询被创建并且被以参数的形式传给了IndexSearcher,就会对查询的结果进行打分,经过一些基础配置的设置之后,控制权将会被传递给Weight实现及其Scorer或者BulkScore实例。在下面算法部分将会有更多的解释。

二、查询类

1、TermQuery

  在众多的Query实现中,TermQuery是最易理解也是在程序中最为常用的。TermQuery匹配包含指定Term的所有文档。一个Term是一个包含在特定的字段中的一个单词。因此,一个TermQuery会获取,包含Term指定的字段中的字符串(或者说单词)的文档,并对这些文档计算分数。构造一个TermQuery的方法如下:

  TermQuery tq = new TermQuery(new Term("fieldName","term"));

  在这个例子中,查询所有在fieldName字段中含有term这个单词的文档。

2、BooleanQuery

  当你将多个TermQuery实例结合起来放到BooleanQuery中使用,就会发生非常有趣的事情。一个BooleanQuery中包含多个BooleanClause。每一个子句中包含一个子查询(查询实例)和一个操作符(来自于BooleanClause.Occur)用于描述子查询如何与其它的子句结合起来。下面介绍一下BooleanClause中的操作符。

MUST 这个操作符用于子句的关键词需要搜索结果中的所有文档都包含。

MUST NOT 这个操作符用于子句的关键词不能出现在搜索结果里的所有文档中。

SHOULD 这个操作符用于子句中的关键词可以出现在搜索结果里的文档中,但这个关键词不是必须的。

FILTER 这个操作符的作用与MUST相同,唯一的区别是这个子句中匹配的结果不参与分数的计算。

构造一个布尔查询需要添加两个以上的BooleanClause实例。但是,如果添加了过多的子句(即BooleanClause),在查询时会抛出TooManyClauses异常。这种情况通常出现在将一个Query重写为包含许多TermQuery子句的BooleanQuery时。默认的可包含的子句的最大数量是1024个,但是,这个值可以通过BooleanQuery.setMaxClauseCount(int)方法改变。

example:

	BooleanQuery.Builder builder = new BooleanQuery.Builder();
    builder.add(new TermQuery(new Term("contents","美国")), BooleanClause.Occur.MUST);
	builder.add(new TermQuery(new Term("contents","中国")), BooleanClause.Occur.MUST_NOT);

3、phrase(短语查询)

  另外一部分查询是关于短语的,去查询文档是否包含指定的需要查询的单词,这部分被称为短语查询。主要有三种不同的处理方式:

 [1]、PhraseQuery——匹配一系列的Term

  PhraseQuery使用一个slop因子来决定在两个Term之间有多少个单词。比如我们有一个PhraseQuery查询:

example:

PhraseQuery phraseQuery = new PhraseQuery(1,"fieldName","quick","fox");

这会匹配到“a quick brown fox”,但是并不能匹配到“the fox is quick”。为什么会这样呢?是因为我们在创建PhraseQuery时传入的两个term(即“quick”和“fox”),以第一个term为基准。当我们设置slop = 1时,表示第二个term移动一次就能够达到第一个term的后一个位置。我们这里所说的一个位置就是一个term。在“a quick brown fox”中,“fox”向前移动一次就能到“quick”后面紧邻的位置,即“brown”所在的位置,而“the fox is quick”中,“fox”需要向后移动三次才能够到quick后面紧邻的位置,所以,将slop设置为1只能匹配到“a quick brown fox”。若是我们将slop设置为3,那么,我们就可以匹配到着两个短语,这是因为,移动次数只要小于slop设置的值即可。

  [2]、MultiPhraseQuery——同位置多个Term查询

  这是一种更为普遍的查询方式,它可以支持在同一个位置上有多个Term,例如,MultiPhraseQuery可以用来执行同义词查询

example:

Term[] terms1 = new Term[]{new Term("contents","中国"),new Term("contents","北京")};
Term[] terms2 = new Term[]{new Term("contents","美国"),new Term("contents","美利坚"),new Term("contents","华盛顿")};
multiPhraseQuery.add(terms1);
multiPhraseQuery.add(terms2);
multiPhraseQuery.setSlop(1);
TopDocs results = indexSearcher.search(multiPhraseQuery.build(),100);

在这个例子中,“中国”与“北京”处在同一个位置上,“美国”与“华盛顿”处在同一个位置上,即把同一个Term[]中的所有Term看作是同义词。这个查询既能匹配到“中国与美国”,“中国与华盛顿”,也能匹配到“北京与美利坚”。通过setSlop()方法还能设置两个Term的slop,这里的slop指的是后面的slop移动次数。

 [3]、SpanNearQuery——匹配其他SpanQuery实例的序列。

  SpanNearQuery允许更复杂的短语查询,因为它是由其他SpanQuery实例构造的,而不仅仅只是由TermQuery实例构造。SpanNearQuery查询临近范围内的Term,可以指定两个Term之间跨度的最大值,即slop,也可以指定给出的Term是否是有序的。在SpanNearQuery中的slop使用与PhrasseQuery中的slop有所不同。这里的slop指的是两个Term中间的距离。看下面的例子:

example:

SpanNearQuery.Builder builder = new SpanNearQuery.Builder("fieldName",true);
builder.addClause(new SpanTermQuery(new Term("contents","中国")));
builder.addClause(new SpanTermQuery(new Term("contents","美国")));
builder.setSlop(1);
TopDocs results = indexSearcher.search(builder.build(),100);

SpanNearQuery.Builder(“fieldName”,in-order)中的fieldName指的是索引中的字段,而in-order是一个Boolean值,为true表示通过addClause()传入的SpanQuery是有序的,false表示无序。上面的例子能够匹配到“中国与美国”,但是不能匹配“美国与中国”,而若是将in-order设置为false,则“美国与中国”也能匹配到。

4、TermRangeQuery

  个人认为在中文查询中并没有什么实际的意义,可以用到英文查询中。TermRangeQuery可以查询指定的两个Term范围之间的Term,Term与Term之间的比较使用BytesRef.compareTo()比较字符的顺序。例如,使用这个Query可以匹配所有包含从a到d之间的字符的Term的文档。如果要查询数字范围,请使用PointRangeQuery。

5、PointRangeQuery

  PointRangeQuery匹配在指定数字范围内的所有文档。若要使用PointRangeQuery,必须使用数值字段,比如IntPoint,LongPoint,FloatPoint或者DoublePoint。

example:

PointRangeQuery pointRangeQuery = (PointRangeQuery) IntPoint.newRangeQuery("Num",1,8);
TopDocs results = indexSearcher.search(pointRangeQuery,10);

这个例子查询“Num”这个字段中保存的值在1到8这个范围内的文档。

6、PrefixQuery

  PrefixQuery实现了前缀查询,在英文中可以有效的使用,但是,应用到中文搜索中,效果并不好,这是因为分词的原因,分词器并不能像我们预想的那样分词。比如,在文档中,一个学校的名字叫“中国工业大学”,一个短语是“中国工人”,如果我们将“中国工”作为前缀,那么这个查询既不能匹配到“中国工业大学”也不能匹配到“中国工人”。那是因为,分词器将“中国工业大学”分成立“中国”、“工业”、“大学”三个词,把“中国工人”分成了“中国”、“工人”两个词,所以,这个查询没有结果返回。在中文搜索中的前缀搜索应用需要在特定的场合中。

example:

PrefixQuery prefixQuery  = new PrefixQuery(new Term("contents","Eas"));

“contents”是查询的字段名,“Eas”是要查询的前缀。

7、WildcardQuery

  尽管PrefixQuery有不同的实现,但是它本质上还是一个WildcardQuery。PrefixQuery允许使用一个前缀查询文档。通配符查询WildcardQuery通过允许使用*(匹配0或更多字符)和?(只匹配一个字符)通配符。注意,WildcardQuery可能非常慢。还要注意,WildcardQuery不应该以*和?开头,因为它们非常慢。一些queryparser默认情况下可能不允许这样做,但是提供了一个setAllowLeadingWildcard方法来取消这个限制。RegexpQuery比WildcardQuery更通用,允许应用程序用正则表达式匹配符合条件的所有文档。

example:

WildcardQuery wildcardQuery = new WildcardQuery(new Term("fieldName","wildcardExp"));

“fieldName”是查询的字段名,“wildcardExp”是你的正则表达式。

8、RegexpQuery

  RegexpQuery基于org.apache.lucene.util.automaton包,提供基于匹配正则表达式的查询。 Term字典以一种智能的方式枚举,以避免比较。有关更多细节,请参见AutomatonQuery。受支持的语法记录在RegExp类中,有关lucene中使用的正则表达式语法,参加RegExp中的Description。注意,这可能与其他正则表达式实现不同。并且,以.*开头的查询可能很慢,因为它需要遍历许多项。为了防止RegexpQueries的低效率查询, Regexp查询语句不应该以.*开头,下面是RegexpQuery的使用方法:

example:

RegexpQuery regexpQuery = new RegexpQuery(new Term("fieldName","regex"));

“fieldName”是查询的字段名,“regex”是你的正则表达式。

9、FuzzyQuery

  FuzzyQuery实现了模糊搜索查询。相似性度量基于damero-Levenshtein(最优字符串对齐)算法,不过可以通过将transposition设置为false来选择使用经典的Levenshtein算法。

example:

FuzzyQuery fuzzyQuery = new FuzzyQuery(new Term("contents","rise"));

三、打分

打分机制介绍

  Lucene的打分机制是Lucene被广泛使用的核心。它的计算速度很快,并且用户不需要了解它内部复杂的细节。简而言之,这种打分机制非常有效,
Lucene计分支持多种可插拔的信息检索模型,包括:

Vector Space Model(VSM)

Probabilistic Models such as Okapi BM25 and DFR

Language models

在lucene中,我们可以使用IndexSearcher.explain(Query,doc)方法来理解某一个匹配到的文档是如何被计算得分的。通常,Query决定哪些文档匹配,而Similarity决定如何为匹配的文档分配分数。

  在lucene中,我们打分的对象是Document。一个Document是一个field的集合。每一个field都包含各自的信息,这些信息包括这个field是如何创建于存储的。需要注意的是,lucene在field层面计算分数,然后将分数合并返回到文档中。这个机制非常重要,因为,如果有两个相同的文档,但是一个文档的文本在两个field中,而另一个文档只有一个field,由于长度归一化,对于同一个查询,两个文档的得分可能不同。

  另外,Lucene允许通过使用BoostQuery来影响查询的各个部分的得分贡献。

通过Similarity改变得分

  通过改变Similarity来影响得分是一个简单方式,这个行为在索引建立的阶段使用IndexWriteConfig.setSimilarity(Similarity),也在查询的阶段,使用IndexSearcher.setSimilarity(Similarity)来改变Similarity。注意,在查询时需要使用与创建索引时相同的Similarity,只有这样,Lucene才能正确的进行相关的操作。

  你可以通过配置不同的内置Similarity实现来影响评分,或者通过调整其参数、子类化它来覆盖行为。一些实现还提供了一个模块化API,你可以通过插入不同的组件(例如term frequency normalizer)来扩展它。最后,您可以直接扩展低级Similarity,以实现新的检索模型,或者使用特定于应用程序的外部评分因子。例如,自定义Similarity可以通过NumericDocValues访问每个文档的值,并将它们集成到分数中。

  参见org.apache.lucene.search.similarities包中的文档,用于提供关于内置可用评分模型和扩展或更改Similarity的信息。

通过改变查询打分策略来改变得分

CustomScoreQuery

在7.0以后的版本已经不建议使用该类,可以使用CustomScoreProvider作为替代

FunctionScoreQuery

同样的,我们也可以使用FunctionScoreQuery影响得分,下面给出一个简单的例子。
example:

DoubleValuesSource doubleValuesSource = DoubleValuesSource.constant(0);
FunctionScoreQuery query = new FunctionScoreQuery(booleanQuery,doubleValuesSource); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值