lucene数值类型的索引和范围查询分析

Query q = NumericRangeQuery.newLongRange("idField", 1L, 10L, true, true);

数值类型建索引的时候,会把数值转换成多个 lexicographic sortable string ,然后索引成 trie 字典树结构。

例如:假设num1 拆解成 a ,ab,abc ;num2 拆解成 a,ab,abd 。

【图1】:

通过搜索ab 可以把带ab 前缀的num1,num2 都找出来。在范围查找的时候,查找范围内的数值的相同前缀可以达到一次查找返回多个doc 的目的,从而减少查找次数。

下面讲解一下 :数值类型的索引和范围查询的工作原理。

1:数值的二进制表示方式

以long 为例:符号位+63位整数位,符号位0表示正数 1表示负数。

对于正数来说低63位越大这个数越大,对于负数来说也是低63位越大。

如果对符号位取反。则long.min -- long.max 可表示为:0x0000,0000,0000,0000  --  0xFFFF,FFFF,FFFF,FFFF

经过这样的转换后, 是不是从字符层面就已经是从小到大排序了?

2:如何拆分前缀

以0x0000,0000,0000,F234为例,每次右移4位。

 1:0x0000,0000,0000,F23   与  0x0000,0000,0000,F230 --0x0000,0000,0000,F23F 范围内的所有数值的前缀一是一致的

 2:0x0000,0000,0000,F2   与  0x0000,0000,0000,F200 ——0x0000,0000,0000,F2FF 范围内的所有数值的前缀一致

 3:0x0000,0000,0000,F  与  0x0000,0000,0000,F000 --0x0000,0000,0000,FFFF 范围内的所有数值的前缀一致

 ....

 0x0

 如果用右移几位后的值做key,可以代表一个相应的范围。key可以理解成数值的前缀

3:对大范围折成小范围

Lucene 在查询时候的法做法是对大范围折成小范围,然后每个小范围分别用前缀进行查找,从而减少查找次数。

4:数值类型的索引的实现

先设定一个PrecisionStep (默认4),对数值类型每次右移(n-1)* PrecisionStep 位。

每次移位后,从左边开始每7位存入一个byte,组成一个byte[],

并且在数组第0位插入一个特殊byte,标识这次的偏移量。

每个byte[]可以转成一个lexicographic sortable string。

lexicographic sortable string 的字符按字典序排列后,和偏移量,数值的大小顺序是一致的。——这个是NumericRangeQuery 范围查找的关键!

long 类型一共64位,如果precisionStep=4,则会有16个lexicographic sortable string。

相当于16个前缀对应一个long数值,再用lucene 的倒序索引,最终索引成类似【图1】 的那种索引结构。

拆分的关键代码:

org.apache.lucene.util.NumericUtils 类的 longToPrefixCodedBytes() 方法

public static void longToPrefixCodedBytes(final long val, final int shift, final BytesRefBuilder bytes) {
    if ((shift & ~0x3f) != 0)  // ensure shift is 0..63
      throw new IllegalArgumentException("Illegal shift value, must be 0..63");
       //计算byte[]的大小,每位七位存入一个byte
    int nChars = (((63-shift)*37)>>8) + 1;    // i/7 is the same as (i*37)>>8 for i in 0..63
       //最后还有第0位存偏移量,所以+1
    bytes.setLength(nChars+1);   // one extra for the byte that contains the shift info
    bytes.grow(BUF_SIZE_LONG);
       //标识偏移量,shift
    bytes.setByteAt(0, (byte)(SHIFT_START_LONG + shift));
       //把符号位取反
    long sortableBits = val ^ 0x8000000000000000L;
       //右移shift位,第一次shifi传0,之后按precisionStep递增
    sortableBits >>>= shift;
    while (nChars > 0) {
      // Store 7 bits per byte for compatibility
      // with UTF-8 encoding of terms
         //每7位存入一上byte ,前面第一位为0——在utf8中表示ascii码.并加到数组中。
      bytes.setByteAt(nChars--, (byte)(sortableBits & 0x7f));
      sortableBits >>>= 7;
    }
  }

5:范围查询

 大致思想是从范围的两端开始拆分。先把低位的值拆成一个区间,再移动PrecisionStep到下一个高位又并成一个区间。

最后把小区间里每个值,按移动的次数,用和索引的同样方式转成lexicographic sortable string.进行查找。

代码:

 org.apache.lucene.util.NumericUtils 类的 splitRange() 方法

private static void splitRange(
    final Object builder, final int valSize,
    final int precisionStep, long minBound, long maxBound
  ) {
    if (precisionStep < 1)
      throw new IllegalArgumentException("precisionStep must be >=1");
    if (minBound > maxBound) return;
    for (int shift=0; ; shift += precisionStep) {
      // calculate new bounds for inner precision
      final long diff = 1L << (shift+precisionStep),
        mask = ((1L<<precisionStep) - 1L) << shift;
      final boolean
        hasLower = (minBound & mask) != 0L,
        hasUpper = (maxBound & mask) != mask;
      final long
        nextMinBound = (hasLower ? (minBound + diff) : minBound) & ~mask,
        nextMaxBound = (hasUpper ? (maxBound - diff) : maxBound) & ~mask;
      final boolean
        lowerWrapped = nextMinBound < minBound,
        upperWrapped = nextMaxBound > maxBound;
      
      if (shift+precisionStep>=valSize || nextMinBound>nextMaxBound || lowerWrapped || upperWrapped) {
        // We are in the lowest precision or the next precision is not available.
        addRange(builder, valSize, minBound, maxBound, shift);
        // exit the split recursion loop
        break;
      }
      
      if (hasLower)
        addRange(builder, valSize, minBound, minBound | mask, shift);
      if (hasUpper)
        addRange(builder, valSize, maxBound & ~mask, maxBound, shift);
      
      // recurse to next precision
      minBound = nextMinBound;
      maxBound = nextMaxBound;
    }
  }

例如:1001,0001-1111,0010 分步拆分成

1: 1001,0001-1001,1111   ( 第0次偏移后  0x91-0x9F  有15个term )

   和 1111,0000 -1111,0010   ( 第0次偏移后 0xF0-0F2  有3个term )

2: 1002,0000 – 1110,1111   右移一次后(0x11- 0x15  有5个term ) 

查找23个lexicographic sortable string.就可以覆盖整个区间。

猜你喜欢

转载自blog.csdn.net/asdfsadfasdfsa/article/details/90644476
今日推荐