最进因为工做的事和国庆,回家了没有环境来写文章,因此搁置了很久,人一懒就不想动,其中有人催我,想一想也不能半途而废了,因为以前也写了些草稿,决心今天必定要写了这玩意。闲话很少说,开始介绍Lucene的查询。数据库
若是将整个Lucene系统当作一个数据库系统也勉强说得过去,由于它拥有完善的存储系统和文件体系结构,Query相对于Lucene来讲便是这个数据库Sql,是获取数据的一个约束,如何可以根据业务来获取数据就须要构建相应的查询条件对索引中得数据进行过滤,获取到咱们的数据,这里主要是说下Lucene的几种查询。架构
先来看下Query这个东西,在Lucene Java中,它定义为一个抽象类,其余的各类具体查询都是继承自它或者它的子类,它里面有几个重要的方法:app
/** Expert: called to re-write queries into primitive queries. For example,oop
* a PrefixQuery will be rewritten into a BooleanQuery that consists学习
* of TermQuerys.测试
*/this
public Query rewrite(IndexReader reader) throws IOException {搜索引擎
return this;spa
}翻译
讲这个方法要联系之前咱们说过的倒排索引的知识,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 、BooleanQuery、MultiTermQuery、SpanQuery,还有一些比较特殊的查询可是偶尔也会用到的查询我这里也会介绍。
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),这些枚举将每一个子查询的匹配要求独立开来匹配数据,而后在经过逻辑来进行组合,最后就是咱们但愿获得的数据了。
BooleanQuery的rewrite干得事情也是相对较简单的,即循环将调用子查询的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的重写都是返回自己,由于都是精确查询,并且全部的SpanQuery的createWeight都是返回一个SpanWeight,而且都重写了
public abstract Spans getSpans(IndexReader reader) throws IOException;
这个方法,用来获取一个获取查询到的全部词在指定包含词的文档中得位置信息,固然咱们再保存索引的时候必须将指定查询的字段的TermVector设置为WITH_POSITIONS_OFFSETS才能获取到位置,而且进行计算,经常使用的子类有SpanNearQuery,这个查询包含一系列的子查询,能够经过参数指定这些子查询在某个文档中匹配到的词之间的最大距离,而且能够指定匹配顺序必须跟查询顺序同样,并且匹配的词的距离越小则得分越高,因此这些词之间是AND查询,这对于一些精确度比较高的场景仍是很是有用的。
最后来介绍2个有用的查询:
一个是MatchllDocsQuery,这个查询功能很简单也很实用,就是能匹配索引中所有的文档,当须要查询索引中得所有的文档的时候,之前我使用过一种方法,就是在全部文档中添加一个静态域,可是发现有这个查询后我才知道那样作彻底是多余的。
还有一个是PayLoadTermQuery,这个查询可以获取指定字段保存的PayLoad信息,而且经过Similarity的scorePayload方法来根据PayLoad来影响文档的得分,关于PayLoad的相关信息,网上有不少文章,之后我也会介绍。
OK我在这里只是简单介绍一个Query的查询工做和一些简单的查询类型分类,若是有兴趣能够去把Lucene的源码下下来而且好好研究下,我这里说的只是Lucene强大查询功能的冰山一角,还有最近在看Hadoop的东西,发现一我的写的东西不管是在代码风格仍是架构风格上都很类似,很容易上手。
最后推荐一个深层次Lucene学习博客,其实这个博客也是从基础讲起,可是要学习须要一点点的耐心,很佩服那位博主http://forfuture1978.iteye.com/blog/ 继续向他学习