lucnen核心之查询
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);