本文共 2772 字,大约阅读时间需要 9 分钟。
简单介绍下 在较早版本的 Lucene 中对一定范围内的查询RanageQuery 。该Query 继承于 MulitTermQuery,在重写(rewrite )Query 树的时候将会遵从一个原则:
根据起始区间值获取term, 然后遍历,根据满足条件的term 的数目来决定重写Query 的类型
如下代码所示:
(图一)具体见M ultiTermQuery.ConstantScoreAutoRewrite.rewrite() 方法
两种方式区别:
方式一:如果区间范围较大,获取terms 较多则采取Filter 过滤的方式遍历以start 开始的term ,获取[start,end] 的范围内的 TermEnum 从而取出docIDSet 。
方式二:如果区间范围不大,获取terms 不多,将区间Query 分解成多个termQuery 独立查询,然后根据BooleanQuery 来合并docId
缺点:
方式一:只支持字符串形式的范围查询,区间满足的term 数据越多,查询性能越差。
方式二:会构造太多termQuery 很可能造成 TooManyClause 异常,而且获取结果再合并将极大影响性能。
因为方式二其实现和普通BooleanQuery --> termQuery 查询方式一致,而本文主要阐述Range 查询,所以将不会方式二实现原理。
OK,那我们看看TermRangeQuery如何实现查询的,我们知道重写Query树后 ,接下来就是生成weight 树,从图一中可以看到方式一中重写的RangeQuey 被包装成 ConstantScoreQuery(newMultiTermQueryWrapperFilter(query)); 那么从下面的代码实现结构可以看到生成的 weight:
ConstantScoreQuery . createWeight()
|
|-- new ConstantScoreQuery.ConstantWeight(searcher);
生成 Weight 树后, weight 树将负责 Scorer 树的生成,如下代码实现结构所示 :
ConstantWeight. Scorer()
|
|-- new ConstantScorer ( similarity , reader, this );
|
|-- DocIdSet docIdSet = MultiTermQueryWrapperFilter .getDocIdSet(reader) ;
|-- DocIdSetIterator iter = docIdSet.iterator();
|-- docIdSetIterator = iter;
Query 树 ->weight 树 ->Scorer 树生成后,将开始打分并收集 docId 的过程。如下所示:
Scorer scorer = weight.scorer();
|
|-- scorer.score(collector);//scorer= ConstantScorer
整个 score 过程是遍历直到取出的值 == NO_MORE_DOCS 。见如下代码所示:
而 nextDoc 由 ConstantScorer 实现:
结合ConstantScorer的构造函数可以看到整个docId的范围过滤都在:
完成,接下来在看看该方法的具体实现:
MultiTermQueryWrapperFilter .getDocIdSet(reader) ;
| // 得到 TermRangeQuery 的 Term 枚举器
|-- final TermEnum enumerator = query .getEnum(reader);
|
|-- new TermRangeTermEnum(reader, field , lowerTerm , upperTerm , includeLower , includeUpper , collator );
TermRangeTermEnum的构造函数 其包含的成员变量如下:
_ String lowerTerm; 左边界字符串
_ String upperTerm; 右边界字符串
_ boolean includeLower; 是否包括左边界
_ boolean includeUpper; 是否包含右边界
_ String field; 域
_ Collator collator; 其允许用户实现其函数 int compare(String source, String target) 来决定怎么样算是大于,怎么样算是小于。
TermRangeTermEnum 来保证满足区间条件的 term 能被 MultiTermQueryWrapperFilter. TermGenerator.generate() 方法收集到OpenBitSet中,如下所示:
而generate()具体实现为:
从上述代码可以看出TermRangeTermEnum最关键的2个方法就是term()和next()方法,
(1) TermRangeTermEnum.term()方法是获取遍历过程中当前的term。
(2) TermRangeTermEnum.next()方法是遍历Term的枚举列表
在 TermRangeTermEnum 并没有重写 next() 方法,所以从父类 FilteredTermEnum 中的 next 可以看到:
从上述代码可以得知遍历结束取决:
(1) endEnum()==true;// 结束枚举遍历
(2) actualEnum.next()==false;// 整个term 的枚举遍历完毕
(3) termCompare(term)==false;// 当前的term 字符串不在[start,end] 区间范围内
而从termRanageTermEnum 中endEnum 其实也是由termCompare(term) 方法来影响,所以人为能影响的区间查询都由termCompare 方法来决定,代码如下所示:
如上代码所述, 区间查询的时间是和区间范围内term 的个数有关系的,也就是说如果区间范围越大,意味着查询的next() term 的次数也会更多。
另外该范围查询只支持字符串范围查询,并不支持数值型的范围查询。所以 从 Lucene 2.9 开始,Lucene 提供对数字范围的支持,但是然而欲使用此查询,必须使用 NumericField 来添加域。 这也是NumericField和numericRangeQuery结合起来的数值型范围查询。
转载地址:http://unqwa.baihongyu.com/