Lucene搜索流程(4.Query)

最进由于工作的事和国庆,回家了没有环境来写文章,所以搁置了好久,人一懒就不想动,其中有人催我,想想也不能半途而废了,由于之前也写了些草稿,决心今天一定要写了这玩意。闲话不多说,开始介绍Lucene的查询。

如果将整个Lucene系统当做一个数据库系统也勉强说得过去,因为它拥有完善的存储系统和文件体系结构,Query相对于Lucene来说即是这个数据库Sql,是获取数据的一个约束,如何能够根据业务来获取数据就需要构建相应的查询条件对索引中得数据进行过滤,获取到我们的数据,这里主要是说下Lucene的几种查询。

先来看下Query这个东西,在Lucene Java中,它定义为一个抽象类,其他的各种具体查询都是继承自它或者它的子类,它里面有几个重要的方法:

 

  /** Expert: called to re-write queries into primitive queries. For example,

   * a PrefixQuery will be rewritten into a BooleanQuery that consists

   * of TermQuerys.

   */

  public Query rewrite(IndexReader reader) throws IOException {

    return this;

  }

讲这个方法要联系以前我们说过的倒排索引的知识,Lucene之所以能够快速查找到某些包含特殊词的文档是因为有了倒排索引,倒排索引的结构是一个词对应着多个文档的一个链表,因此如果我们知道一个词的话就能很快查找到,但是这种结构确不适用于模糊查找,说到这里我想说个事,在我做搜索引擎的时候,那些测试的人老是:哎呀呀,曾杰,你这个搜索引擎有问题啊,你看我输入一个字没把所有包含这个字的数据给我找出来啊。遇到这样的情况我先是满头黑线,然后在解释说这个搜索引擎并不是同数据库的like。。。。题外话了。那如果的确有这种情况怎么办呢?Lucene早就想到了,提供了一个WildcardQuery,可以根据这个查询提供的词先模糊匹配出所有包含这个字符的Term,然后根据这些Term去查找指定的Document,其中根据这个查询提供的词先模糊匹配出所有包含这个字符的Term这一步的实现就是要靠这方法来实现,通过这个方法可以将一些查询转换成指定的Term的查询,至于怎么实现我们在下面再细说。那在什么时候Lucene会调用这个方法呢?在调用IndexSearcher. search(Query query, int n)的时候最终会调用createNormalizedWeight(query)这个放方法,Searcher的默认实现里面便会通过调用rewrite来重写查询。

  /**

   * Expert: Constructs an appropriate Weight implementation for this query.

   *

   * <p>

   * Only implemented by primitive queries, which re-write to themselves.

   */

  public Weight createWeight(Searcher searcher) throws IOException {

    throw new UnsupportedOperationException("Query " + this + " does not implement createWeight");

  }

这个方法也是个核心的方法,一个查询最后获取的数据就是通过这个方法先创建一个Weight(权重),这个东西能简单来说就是一个计算这个查询的得分权重,并且能返回一个匹配到的文档的枚举的对象,Lucene在查询的时候会调用这个方法自动生成一个Weight然后从Weight中获取一个匹配到的文档的枚举器Scorer,然后用Weight对这些文档进行打分,最后返回给用户,其重要性就不言而喻了吧。Weight的具体工作方法我们以后再细说。

  /** Sets the boost for this query clause to <code>b</code>.  Documents

   * matching this clause will (in addition to the normal weightings) have

   * their score multiplied by <code>b</code>.

   */

  public void setBoost(float b) { boost = b; }

 

  /** Gets the boost for this clause.  Documents matching

   * this clause will (in addition to the normal weightings) have their score

   * multiplied by <code>b</code>.   The boost is 1.0 by default.

   */

  public float getBoost() { return boost; }

最后这两个方法就是很常用的开发API了,就是设置和获取查询权重,这个查询权重简单的描述了这个查询在本次查询中得重要性,重要性越高,这个查询匹配到的文档得分自然越高。

Query介绍完了就介绍下他的子类。

Query的子类大体上可以分为三种,TermQuery BooleanQueryMultiTermQuerySpanQuery,还有一些比较特殊的查询但是偶尔也会用到的查询我这里也会介绍。

TermQuery可以说是Lucene中一种最底层也是最普遍的的查询了,它就是简单的实现了倒排索引的概念,通过一个词来获取包含这个词的文档,TermQuery的重写方法rewrite就是简单的返回了this,因为这个是最底层的查询了,自然不需要加工了。一个TermQuery通过一个Term来构造,即查询出包含这个Term的相关文档,这种概念理解起来还是很容易的。

BooleanQuery:其实Lucene的查询逻辑既是现实了一个查询树的概念,通过查询树来对数据进行层层过滤就能得到我们想要的数据,BooleanQuery相当于这个书中的一个个节点,但绝对不是叶子节点,最终的查询功能还是得靠具体的叶子节点去实现的,往一个节点中添加一个子节点的代码为:

  public void add(Query query, BooleanClause.Occur occur) {

    add(new BooleanClause(query, occur));

  }

其中occur为一个逻辑描述枚举,这三个值为SHOULD(可以满足也可以不满足,但不是必要条件,相当于OR)、MUST(数据必须满足此条件,相当于AND)、MUST_NOT(数据必须不在此条件范围中,相当于NOT),这些枚举将每个子查询的匹配要求独立开来匹配数据,然后在通过逻辑来进行组合,最后就是我们希望得到的数据了。

BooleanQueryrewrite干得事情也是相对较简单的,即循环将调用子查询的rewrite方法进行重写,这应该算是一种递归的实现。

 

最后介绍的是MultiTermQuery,这个是Lucene里面最复杂的一种查询了,其中的变种也是最多的,也正是通过它实现了Lucene的强大的查询功能。

MultiTermQuery有几个方法来实现多词查询

  /** Construct the enumeration to be used, expanding the pattern term. */

  protected abstract FilteredTermEnum getEnum(IndexReader reader)

      throws IOException;

这个方法即是获取重写后的一个词的枚举器,这个方法是通过在MultiTermQuery中定义的一个内部静态类RewriteMethod中调用的,其实MultiTermQuery的重写是通过RewriteMethod这个类来进行的,通过调用RewriteMethod.rewrite(IndexReader reader, MultiTermQuery query)来重写这个查询,一般的实现为在RewriteMethod中将FilteredTermEnum中得多个词组合成一个BooleanQuery,这些条件的组合逻辑为SHOULD

其中FilteredTermEnum相对于普通的TermEnum来说多了几个方法

用来比较一个词是否是当前匹配到的,如果是的返回true否则返回false,如果返回false则同时代表这个迭代器到了末端

    protected abstract boolean termCompare(Term term);

   

    //这个是用来定义一个匹配到的词的相关性(这个地方是根据注释翻译出来的,具体作用应该是定义如果一个词被匹配到然后拥有的权重)

    public abstract float difference();

 

    //这个方法用来获取当前枚举是否已经到了末端的标示

    protected abstract boolean endEnum();

 

通过这几个方法就可以将多个词重写出来然后再通过实际的TermQuery进行最终查询。

 

如果想要了解更多关于MultiTermQuery建议先看看WildCardQuery的源码,是比较简单和明了的。

 

SpanQuery其实是Query的一个特殊变种,它的子类可以通过对他的查询进行一个词的位置运算,所有的SpanQuery的重写都是返回本身,因为都是精确查询,而且所有的SpanQuerycreateWeight都是返回一个SpanWeight,并且都重写了

 

  public abstract Spans getSpans(IndexReader reader) throws IOException;

这个方法,用来获取一个获取查询到的所有词在指定包含词的文档中得位置信息,当然我们再保存索引的时候必须将指定查询的字段的TermVector设置为WITH_POSITIONS_OFFSETS才能获取到位置,并且进行计算,常用的子类有SpanNearQuery,这个查询包含一系列的子查询,可以通过参数指定这些子查询在某个文档中匹配到的词之间的最大距离,并且可以指定匹配顺序必须跟查询顺序一样,而且匹配的词的距离越小则得分越高,所以这些词之间是AND查询,这对于一些精确度比较高的场景还是非常有用的。

 

 

最后来介绍2个有用的查询:

一个是MatchllDocsQuery,这个查询功能很简单也很实用,就是能匹配索引中全部的文档,当需要查询索引中得全部的文档的时候,以前我使用过一种方法,就是在所有文档中添加一个静态域,但是发现有这个查询后我才知道那样做完全是多余的。

还有一个是PayLoadTermQuery,这个查询能够获取指定字段保存的PayLoad信息,并且通过SimilarityscorePayload方法来根据PayLoad来影响文档的得分,关于PayLoad的相关信息,网上有很多文章,以后我也会介绍。

 

OK我在这里只是简单介绍一个Query的查询工作和一些简单的查询类型分类,如果有兴趣可以去把Lucene的源码下下来并且好好研究下,我这里说的只是Lucene强大查询功能的冰山一角,还有最近在看Hadoop的东西,发现一个人写的东西无论是在代码风格还是架构风格上都很相似,很容易上手。

最后推荐一个深层次Lucene学习博客,其实这个博客也是从基础讲起,但是要学习需要一点点的耐心,很佩服那位博主http://forfuture1978.iteye.com/blog/  继续向他学习

转载于:https://my.oschina.net/zengjie/blog/82375

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值