背景
本文介绍第一种DocValue类型:NumericDocValues。NumericDocValues是其他一些DocValue的基础,所以我们先行介绍。NumericDocValues是数值类型的DocValues,底层只用long来存储,因此如果业务中非long的数值类型需要转化成long来使用NumericDocValues。
前置知识
本文涉及到的一些工具在之前的文章中都做了详细的介绍,后续碰到不会重复介绍。
- DirectWriter:用来压缩存储long集合,详见《多值编码压缩算法》
- Packed64:用来压缩存储long集合,详见《多值编码压缩算法》
- IndexedDISI:存储所有包含DocValues字段的docID,详见《可持久化的位图实现方案》
NumericDocValues的存储类型
对于NumericDocValues中值的分布的不同情况,Lucene提供了多种不同的存储方式,官方文档中为不同存储方式取名及介绍其实并没有十分准确,这里我们参考官方的命名格式做了更合适的描述:
-
table-compressed:
如果所有的NumericDocValues中存在大量的重复value,去重后的value总数不超过256个,则把这些value排序之后存储在数组中,数组的下标就是对应value的编号,这个数组作为元信息存储在dvm文件中,数据就只存储编号,存储在dvd文件中。
-
single-block-compressed:
如果NumericDocValues去重后的value总数超过256个,则找出最小值,所有的值都和最小值做差。比如对于数据(15,35,20,25,45),原来每个value的编码至少需要6bits,如果都与最小值15做差,得到(0,20,5,10,30),那每个value最多需要5bits。还可以继续优化,(15,35,20,25,45)的最小公倍数是5,如果再除以最小公倍数就得到了(0,4,1,2,6),这样每个value最多只需要3bits。
-
multi-blocks-compressed:
如果数据量很大的话,则max-min会因为某个值特别大或者特别小导致max-min也很大,起不到压缩的效果,而且数据量大的情况,gcd一般都是1。所以可以分block来处理,把这种特别大或者特别小的值的影响控制在一个block中。没有全局数据信息的时候,无法判断分block是否一定是比不分block的压缩效果好,所以Lucene中会预先统计两者使用的bits数量,如果分block存储相对于single-block-compressed能有10%的存储空间优化,则使用分block的方式存储。
-
Const-compressed:
如果所有的值都相同,这种特殊情况,只存储一个值就行了。
文件格式
dvm
整体结构
字段详解
- FieldNumber:字段的编号
- DocValueType:字段的docValue类型编号
- DocsWithField:存在此field的docID集合,分为3种情况:
- 所有doc都不包含
- DocsWithFieldOffset:-2,表示所有doc都不包含情况
- DocsWithFieldLength:0
- JumpTableEntryCount:-1
- DenseRankPower:-1
- 所有doc都包含
- DocsWithFieldOffset:-1,表示所有的doc都包含情况
- DocsWithFieldLength:0
- JumpTableEntryCount:-1
- DenseRankPower:-1
- 部分doc包含:使用IndexDISI来存储
- DocsWithFieldOffset:IndexDISI在dvd文件中的起始位置
- DocsWithFieldLength:IndexDISI在dvd文件中的总长度
- JumpTableEntryCount:IndexDISI中jump的参数
- DenseRankPower:IndexDISI中rank的参数
- 所有doc都不包含
- NumValues:一共有多少个value
- Table:table-compressed存储结构的元信息
- TableSize:
- const-compressed或者是single-block-compressed,则TableSize=-1。后面在通过NumBitsPerValue字段来区分这两种情况,如果NumBitsPerValue=0,则是const-compressed,value读取Min字段就可以。否则就是single-block-compressed,按照single-block-compressed的存储方式来读取即可。
- 如果使用multi-blocks-compressed存储,则TableSize=-2-NumericBlockShift,NumericBlockShift决定了block的大小。
- 如果使用table-compressed存储,则TableSize就是去重后值的个数
- TableValues:只有使用table-compressed存储的时候,该值才存在,并且存储的是去重后且排序的值。
- TableSize:
- NumBitsPerValue
- table-compressed:uniqueValues.size() - 1所需的bit
- multi-blocks-compressed:0xFF
- single-block-compressed:(max - min) / gcd
- Min:所有的value都会转成(value- min) / gcd。这个min是给single-block-compressed和table-compressed使用的。multi-blocks-compressed每个block都有一个min,存储在dvd文件中。
- GCD:所有的value都会转成(value- min) / gcd
- DataStartOffset:该field的docValues数据在dvd文件的起始位置
- DataLength:该field的docValues数据在dvd文件的总长度
- JumpTableOffset:如果是multi-blocks-compressed,则存储的是所有block的索引信息在dvd文件的起始位置。否则是-1。
dvd
整体结构
字段详解
NumericDocValues的dvd文件也是按照存储格式来区分的:
const-compressed
所有的信息都在dvm文件中。
table-compressed
把所有的value都转成唯一编号,然后使用DirectWriter存储。
single-block-compressed
把所有的value都转成 (value- min) / gcd,然后使用DirectWriter存储。
multi-blocks-compressed
-
BlockDatas:block的数据
-
BlockData:有两种情况
(1)所有的值都相等:
- 0:标记位,表示这种情况
- Value:都相等的那个值
(2)使用DirectWriter存储
- BitsPerValue:block中每个value占用的bit
- Min:block中的最小值
- BlockDataLength:block的存储空间大小
- DirectWriter:使用DirectWriter存储数据
-
-
BlockOffsets:block的索引,也是block的起始位置
- BlockOffset:对应的block的起始位置
源码解析
构建
数据收集
NumericDocValues临时存储的工具是PackedLongValues,每处理256个value使用的是Packed64进行压缩存储。
在持久化的时候,会把NumericDocValues的值和包含该field的docID集合会根据是否需要对doc进行排序都封装到BufferedNumericDocValues或者SortingNumericDocValues中。
这里需要介绍下doc排序相关的知识,我们在索引的时候,可以指定按某些条件对所有doc进行排序,这样是为了在检索的时候,可以根据时间或者召回个数进行截断。而排序的条件一般就是按1个字段或者多个字段的值进行排序。在这里,我们了解下即可,以后介绍索引的整个流程时会详细介绍。
NumericDocValuesWriter
class NumericDocValuesWriter extends DocValuesWriter<NumericDocValues> {
// 用来构建PackedLongValues
private final PackedLongValues.Builder pending;
// 最终docValues都存储在PackedLongValues
private PackedLongValues finalValues;
// 全局内存统计
private final Counter iwBytesUsed;
// 收集NumericDocValues使用到的内存
private long bytesUsed;
// 用来记录包含此docValues字段的docId集合
private DocsWithFieldSet docsWithField;
// 字段的元信息
private final FieldInfo fieldInfo;
// 前一个docID,用来判断docID是否出现过
private int lastDocID = -1;
NumericDocValuesWriter(FieldInfo fieldInfo, Counter iwBytesUsed) {
pending = PackedLongValues.deltaPackedBuilder(PackedInts.COMPACT);
docsWithField = new DocsWithFieldSet();
bytesUsed = pending.ramBytesUsed() + docsWithField.ramBytesUsed();
this.fieldInfo = fieldInfo;
this.iwBytesUsed = iwBytesUsed;
iwBytesUsed.addAndGet(bytesUsed);
}
// 新增一个value
public void addValue(int docID, long value) {
// docID必须是严格递增的,也就是一个docID只能有一个NumericDocValues
if (docID <= lastDocID) {
throw new IllegalArgumentException(
"DocValuesField \""
+ fieldInfo.name
+ "\" appears more than once in this document (only one value is allowed per field)");
}
// 把value临时存起来
pending.add(value);
// 记录docId
docsWithField.add(docID);
// 更新内存使用大小
updateBytesUsed();
lastDocID = docID;
}
private void updateBytesUsed() {
final long newBytesUsed = pending.ramBytesUsed() + docsWithField.ramBytesUsed();
iwBytesUsed.addAndGet(newBytesUsed - bytesUsed);
bytesUsed = newBytesUsed;
}
@Override
NumericDocValues getDocValues() {
if (finalValues == null) {
finalValues = pending.build();
}
// 把结果封装成 BufferedNumericDocValues,可以获取到所有的docID对应的docValues
return new BufferedNumericDocValues(finalValues, docsWithField.iterator());
}
// 根据 sortMap 中的排序信息排序,sortMap中的oldToNew方法,输入是docID,输出是根据排序得到的新的docID
static NumericDVs sortDocValues(int maxDoc, Sorter.DocMap sortMap, NumericDocValues oldDocValues)
throws IOException {
FixedBitSet docsWithField = new FixedBitSet(maxDoc);
long[] values = new long[maxDoc];
while (true) {
int docID = oldDocValues.nextDoc();
if (docID == NO_MORE_DOCS) {
break;
}
int newDocID = sortMap.oldToNew(docID);
docsWithField.set(newDocID);
values[newDocID] = oldDocValues.longValue();
}
// NumericDVs中封装的是排序后的docID和对应的docValues
return new NumericDVs(values, docsWithField);
}
@Override
public void flush(SegmentWriteState state, Sorter.DocMap sortMap, DocValuesConsumer dvConsumer)
throws IOException {
if (finalValues == null) { // 固定values
finalValues = pending.build();
}
final NumericDVs sorted;
if (sortMap != null) {
NumericDocValues oldValues =
new BufferedNumericDocValues(finalValues, docsWithField.iterator());
sorted = sortDocValues(state.segmentInfo.maxDoc(), sortMap, oldValues);
} else {
sorted = null;
}
// dvConsumer就是Lucene90DocValuesConsumer
dvConsumer.addNumericField(
fieldInfo,
new EmptyDocValuesProducer() {
@Override
public NumericDocValues getNumeric(FieldInfo fieldInfo) {
if (fieldInfo != NumericDocValuesWriter.this.fieldInfo) {
throw new IllegalArgumentException("wrong fieldInfo");
}
if (sorted == null) { // 不需要对doc排序,则按docID的处理顺序存储
return new BufferedNumericDocValues(finalValues, docsWithField.iterator());
} else { // 按照指定的doc排序存储
return new SortingNumericDocValues(sorted);
}
}
});
}
}
BufferedNumericDocValues
static class BufferedNumericDocValues extends NumericDocValues {
// value
final PackedLongValues.Iterator iter;
// docID
final DocIdSetIterator docsWithField;
// 当前docID对应的value
private long value;
BufferedNumericDocValues(PackedLongValues values, DocIdSetIterator docsWithFields) {
this.iter = values.iterator();
this.docsWithField = docsWithFields;
}
@Override
public int docID() {
return docsWithField.docID();
}
@Override
public int nextDoc() throws IOException {
int docID = docsWithField.nextDoc();
if (docID != NO_MORE_DOCS) {
value = iter.next();
}
return docID;
}
@Override
public int advance(int target) {
throw new UnsupportedOperationException();
}
@Override
public boolean advanceExact(int target) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public long cost() {
return docsWithField.cost();
}
@Override
public long longValue() {
return value;
}
}
SortingNumericDocValues
static class SortingNumericDocValues extends NumericDocValues {
// 存储的是按指定顺序排好序的docID和对应的docValues
private final NumericDVs dvs;
private int docID = -1;
private long cost = -1;
SortingNumericDocValues(NumericDVs dvs) {
this.dvs = dvs;
}
@Override
public int docID() {
return docID;
}
@Override
public int nextDoc() {
if (docID + 1 == dvs.docsWithField.length()) {
docID = NO_MORE_DOCS;
} else {
docID = dvs.docsWithField.nextSetBit(docID + 1);
}
return docID;
}
@Override
public int advance(int target) {
throw new UnsupportedOperationException("use nextDoc() instead");
}
@Override
public boolean advanceExact(int target) throws IOException {
// needed in IndexSorter#{Long|Int|Double|Float}Sorter
docID = target;
return dvs.docsWithField.get(target);
}
@Override
public long longValue() {
return dvs.values[docID];
}
@Override
public long cost() {
if (cost == -1) {
cost = dvs.docsWithField.cardinality();
}
return cost;
}
}
持久化
NumericDocValues持久化的核心逻辑在writeValues方法中,主要做了以下几件事:
- 遍历所有的value,做一些评估,确定使用哪种存储方式
- 计算所有value的gcd
- 记录去重的value个数,如果超过256就不使用table-compressed
- 记录multi-blocks-compressed和single-block-compressed分别使用的bits总数(借助MinMaxTracker工具类,比较简单,自行查看)
- 记录本字段的docValue元信息
- 记录存在本字段的docID集合
- 根据存储方式存储docValue数据
public void addNumericField(FieldInfo field, DocValuesProducer valuesProducer)
throws IOException {
// 字段的编号
meta.writeInt(field.number);
// docValue的类型
meta.writeByte(Lucene90DocValuesFormat.NUMERIC);
// 决定使用哪种存储方式的逻辑都在这里面
writeValues(
field,
new EmptyDocValuesProducer() {
@Override
public SortedNumericDocValues getSortedNumeric(FieldInfo field) throws IOException {
return DocValues.singleton(valuesProducer.getNumeric(field));
}
},
false);
}
private long[] writeValues(FieldInfo field, DocValuesProducer valuesProducer, boolean ords)
throws IOException {
SortedNumericDocValues values = valuesProducer.getSortedNumeric(field);
// 获取第一个value,所有的value都会减去这个值再算gcd
// MR的commit信息说这样merge的时候能加速10%左右,不懂什么原理,先留个坑,以后明白了再来补充
final long firstValue;
if (values.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
firstValue = values.nextValue();
} else {
firstValue = 0L;
}
// values是迭代器,需要重新获取
values = valuesProducer.getSortedNumeric(field);
// 多少个doc存在该field的docValue
int numDocsWithValue = 0;
// 全局的最大最小值跟踪器
MinMaxTracker minMax = new MinMaxTracker();
// 每个block的最大最小值跟踪器
MinMaxTracker blockMinMax = new MinMaxTracker();
// 所有value的gcd
long gcd = 0;
// 用来统计唯一值有多少个,临界是256,不超过的话有特殊的存储方式
Set<Long> uniqueValues = ords ? null : new HashSet<>();
// 遍历所有的doc
for (int doc = values.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = values.nextDoc()) {
// 遍历doc中所有的value
for (int i = 0, count = values.docValueCount(); i < count; ++i) {
long v = values.nextValue();
// 更新gcd
if (gcd != 1) {
if (v < Long.MIN_VALUE / 2 || v > Long.MAX_VALUE / 2) {
gcd = 1;
} else {
gcd = MathUtil.gcd(gcd, v - firstValue);
}
}
// 更新当前block的最大最小值
blockMinMax.update(v);
if (blockMinMax.numValues == NUMERIC_BLOCK_SIZE) { // 如果block满了
// 更新全局的最大最小值
minMax.update(blockMinMax);
blockMinMax.nextBlock();
}
// 如果唯一值超过了256,则不需要再继续统计了
if (uniqueValues != null && uniqueValues.add(v) && uniqueValues.size() > 256) {
uniqueValues = null;
}
}
numDocsWithValue++;
}
minMax.update(blockMinMax);
minMax.finish();
blockMinMax.finish();
final long numValues = minMax.numValues;
long min = minMax.min;
final long max = minMax.max;
assert blockMinMax.spaceInBits <= minMax.spaceInBits;
// 存储存在该field的docID,有三种方式
// 在dvm文件中会有四个字段,其实是使用IndexDISI的时候需要:
// 1.docsWithFieldOffset
// 2.docsWithFieldLength
// 3.jumpTableEntryCount
// 4.denseRankPower
if (numDocsWithValue == 0) { // 如果所有的文档都不存在该字段
meta.writeLong(-2);
meta.writeLong(0L);
meta.writeShort((short) -1);
meta.writeByte((byte) -1);
} else if (numDocsWithValue == maxDoc) { // 所有的文档都存在该字段
meta.writeLong(-1);
meta.writeLong(0L);
meta.writeShort((short) -1);
meta.writeByte((byte) -1);
} else { // 部分文档存在该字段,使用IndexedDISI存储所有的docID
// 在dvd文件中的起始位置
long offset = data.getFilePointer();
meta.writeLong(offset);
values = valuesProducer.getSortedNumeric(field);
final short jumpTableEntryCount =
IndexedDISI.writeBitSet(values, data, IndexedDISI.DEFAULT_DENSE_RANK_POWER);
meta.writeLong(data.getFilePointer() - offset);
meta.writeShort(jumpTableEntryCount);
meta.writeByte(IndexedDISI.DEFAULT_DENSE_RANK_POWER);
}
// 元信息记录一共有多少个value
meta.writeLong(numValues);
final int numBitsPerValue;
// 判断是否使用multi-blocks-compressed存储方式
boolean doBlocks = false;
// table-compressed存储方式使用
Map<Long, Integer> encode = null;
if (min >= max) { // 所有的value都是0
numBitsPerValue = 0;
meta.writeInt(-1); // tablesize
} else {
// single-block-compressed使用的空间:DirectWriter.unsignedBitsRequired((max - min) / gcd)
// table-compressed使用的空间:DirectWriter.unsignedBitsRequired(uniqueValues.size() - 1)
if (uniqueValues != null
&& uniqueValues.size() > 1
&& DirectWriter.unsignedBitsRequired(uniqueValues.size() - 1)
< DirectWriter.unsignedBitsRequired((max - min) / gcd)) {
numBitsPerValue = DirectWriter.unsignedBitsRequired(uniqueValues.size() - 1);
final Long[] sortedUniqueValues = uniqueValues.toArray(new Long[0]);
// 对所有的唯一值进行排序。源码看到现在,没发现排序有什么用,判断某个值是否存在?以后有结论了来修改
Arrays.sort(sortedUniqueValues);
// 使用table-compressed的时候存储的是tableSize
meta.writeInt(sortedUniqueValues.length);
for (Long v : sortedUniqueValues) {
meta.writeLong(v);
}
// key是value,值是编码,其实就是数组的下标
encode = new HashMap<>();
for (int i = 0; i < sortedUniqueValues.length; ++i) {
encode.put(sortedUniqueValues[i], i);
}
min = 0;
gcd = 1;
} else {
uniqueValues = null;
// 如果multi-blocks-compressed比single-block-compressed的存储空间优化10%以上,
// 则使用multi-blocks-compressed
doBlocks =
minMax.spaceInBits > 0 && (double) blockMinMax.spaceInBits / minMax.spaceInBits <= 0.9;
if (doBlocks) {
numBitsPerValue = 0xFF;
// multi-blocks-compressed存储方式,tableSize= -2 - NUMERIC_BLOCK_SHIFT
meta.writeInt(-2 - NUMERIC_BLOCK_SHIFT);
} else {
numBitsPerValue = DirectWriter.unsignedBitsRequired((max - min) / gcd);
if (gcd == 1
&& min > 0
&& DirectWriter.unsignedBitsRequired(max)
== DirectWriter.unsignedBitsRequired(max - min)) {
// 把min设为0,这样读取的时候不需要额外的计算
min = 0;
}
// single-block-compressed存储方式,tableSize= -1
meta.writeInt(-1);
}
}
}
meta.writeByte((byte) numBitsPerValue);
meta.writeLong(min);
meta.writeLong(gcd);
// 当前字段的docvalues在dvd文件中的起始位置
long startOffset = data.getFilePointer();
meta.writeLong(startOffset);
// 使用multi-blocks-compressed存储方式时,block的索引在dvd文件中的起始位置
long jumpTableOffset = -1;
if (doBlocks) {
jumpTableOffset = writeValuesMultipleBlocks(valuesProducer.getSortedNumeric(field), gcd);
} else if (numBitsPerValue != 0) {
// 使用DirectWriter存储
writeValuesSingleBlock(
valuesProducer.getSortedNumeric(field), numValues, numBitsPerValue, min, gcd, encode);
}
// 当前field的docvalues在dvd文件中的data length
meta.writeLong(data.getFilePointer() - startOffset);
meta.writeLong(jumpTableOffset);
return new long[] {numDocsWithValue, numValues};
}
private void writeValuesSingleBlock(
SortedNumericDocValues values,
long numValues,
int numBitsPerValue,
long min,
long gcd,
Map<Long, Integer> encode)
throws IOException {
DirectWriter writer = DirectWriter.getInstance(data, numValues, numBitsPerValue);
for (int doc = values.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = values.nextDoc()) {
for (int i = 0, count = values.docValueCount(); i < count; ++i) {
long v = values.nextValue();
if (encode == null) {
writer.add((v - min) / gcd);
} else { // 如果使用的是table-compressed,则存储的value的编码
writer.add(encode.get(v));
}
}
}
writer.finish();
}
// Returns the offset to the jump-table for vBPV
private long writeValuesMultipleBlocks(SortedNumericDocValues values, long gcd)
throws IOException {
long[] offsets = new long[ArrayUtil.oversize(1, Long.BYTES)];
int offsetsIndex = 0;
final long[] buffer = new long[NUMERIC_BLOCK_SIZE];
final ByteBuffersDataOutput encodeBuffer = ByteBuffersDataOutput.newResettableInstance();
int upTo = 0;
for (int doc = values.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS; doc = values.nextDoc()) {
for (int i = 0, count = values.docValueCount(); i < count; ++i) {
buffer[upTo++] = values.nextValue();
if (upTo == NUMERIC_BLOCK_SIZE) { // 生成一个block
offsets = ArrayUtil.grow(offsets, offsetsIndex + 1);
// block在dvd文件中的起始位置
offsets[offsetsIndex++] = data.getFilePointer();
writeBlock(buffer, NUMERIC_BLOCK_SIZE, gcd, encodeBuffer);
upTo = 0;
}
}
}
if (upTo > 0) { // 最后一个block
offsets = ArrayUtil.grow(offsets, offsetsIndex + 1);
offsets[offsetsIndex++] = data.getFilePointer();
writeBlock(buffer, upTo, gcd, encodeBuffer);
}
// 记录block的索引,也就是每个block在dvd文件中的起始位置
final long offsetsOrigo = data.getFilePointer();
for (int i = 0; i < offsetsIndex; i++) {
data.writeLong(offsets[i]);
}
data.writeLong(offsetsOrigo);
return offsetsOrigo;
}
private void writeBlock(long[] values, int length, long gcd, ByteBuffersDataOutput buffer)
throws IOException {
assert length > 0;
long min = values[0];
long max = values[0];
for (int i = 1; i < length; ++i) {
final long v = values[i];
assert Math.floorMod(values[i] - min, gcd) == 0;
min = Math.min(min, v);
max = Math.max(max, v);
}
if (min == max) { // 所有值都相等
data.writeByte((byte) 0);
data.writeLong(min);
} else { // 使用DirectWriter存储
final int bitsPerValue = DirectWriter.unsignedBitsRequired((max - min) / gcd);
buffer.reset();
assert buffer.size() == 0;
final DirectWriter w = DirectWriter.getInstance(buffer, length, bitsPerValue);
for (int i = 0; i < length; ++i) {
w.add((values[i] - min) / gcd);
}
w.finish();
data.writeByte((byte) bitsPerValue);
data.writeLong(min);
data.writeInt(Math.toIntExact(buffer.size()));
buffer.copyTo(data);
}
}
读取
其实如果清楚了文件格式中各个字段含义,读取的逻辑是比较简单:
先判断使用的是哪种存储方式,按照对应的存储方式格式来解析数据,构造成可读取的对象。
private NumericEntry readNumeric(IndexInput meta) throws IOException {
NumericEntry entry = new NumericEntry();
// 把NumericDocValues的元信息解析存储在entry中
readNumeric(meta, entry);
return entry;
}
private void readNumeric(IndexInput meta, NumericEntry entry) throws IOException {
entry.docsWithFieldOffset = meta.readLong();
entry.docsWithFieldLength = meta.readLong();
entry.jumpTableEntryCount = meta.readShort();
entry.denseRankPower = meta.readByte();
entry.numValues = meta.readLong();
// 用来判断是哪种存储方式的
int tableSize = meta.readInt();
if (tableSize > 256) { // 非法值
throw new CorruptIndexException("invalid table size: " + tableSize, meta);
}
if (tableSize >= 0) { // table-compressed存储方式
entry.table = new long[tableSize];
for (int i = 0; i < tableSize; ++i) { // 读取所有类别的value,数据table的下标就是编号
entry.table[i] = meta.readLong();
}
}
if (tableSize < -1) { // multi-blocks-compressed存储方式
entry.blockShift = -2 - tableSize;
} else { // single-blocks-compressed存储方式
entry.blockShift = -1;
}
entry.bitsPerValue = meta.readByte();
entry.minValue = meta.readLong();
entry.gcd = meta.readLong();
entry.valuesOffset = meta.readLong();
entry.valuesLength = meta.readLong();
entry.valueJumpTableOffset = meta.readLong();
}
private NumericDocValues getNumeric(NumericEntry entry) throws IOException {
if (entry.docsWithFieldOffset == -2) { // 所有doc都不包含该field
return DocValues.emptyNumeric();
} else if (entry.docsWithFieldOffset == -1) { // 所有doc都包含该field
if (entry.bitsPerValue == 0) { // 所有的value都相等
return new DenseNumericDocValues(maxDoc) {
@Override
public long longValue() throws IOException {
return entry.minValue;
}
};
} else {
final RandomAccessInput slice =
data.randomAccessSlice(entry.valuesOffset, entry.valuesLength);
if (entry.blockShift >= 0) { // multi-blocks-compressed
return new DenseNumericDocValues(maxDoc) {
final VaryingBPVReader vBPVReader = new VaryingBPVReader(entry, slice);
@Override
public long longValue() throws IOException {
return vBPVReader.getLongValue(doc);
}
};
} else {
final LongValues values =
getDirectReaderInstance(slice, entry.bitsPerValue, 0L, entry.numValues);
if (entry.table != null) { // table-compressed
final long[] table = entry.table;
return new DenseNumericDocValues(maxDoc) {
@Override
public long longValue() throws IOException {
return table[(int) values.get(doc)];
}
};
} else if (entry.gcd == 1 && entry.minValue == 0) {
// gcd是1,min是0的情况,可以直接读取,不需要做 value * gcd + min 的还原计算
return new DenseNumericDocValues(maxDoc) {
@Override
public long longValue() throws IOException {
return values.get(doc);
}
};
} else {
final long mul = entry.gcd;
final long delta = entry.minValue;
return new DenseNumericDocValues(maxDoc) {
@Override
public long longValue() throws IOException {
return mul * values.get(doc) + delta;
}
};
}
}
}
} else {// 部分doc包含field
final IndexedDISI disi =
new IndexedDISI(
data,
entry.docsWithFieldOffset,
entry.docsWithFieldLength,
entry.jumpTableEntryCount,
entry.denseRankPower,
entry.numValues);
if (entry.bitsPerValue == 0) { // 所有值都一样
return new SparseNumericDocValues(disi) {
@Override
public long longValue() throws IOException {
return entry.minValue;
}
};
} else {
final RandomAccessInput slice =
data.randomAccessSlice(entry.valuesOffset, entry.valuesLength);
if (entry.blockShift >= 0) { // multi-blocks-compressed
return new SparseNumericDocValues(disi) {
final VaryingBPVReader vBPVReader = new VaryingBPVReader(entry, slice);
@Override
public long longValue() throws IOException {
final int index = disi.index();
return vBPVReader.getLongValue(index);
}
};
} else { // single-block-compressed
final LongValues values =
getDirectReaderInstance(slice, entry.bitsPerValue, 0L, entry.numValues);
if (entry.table != null) { // table-compressed
final long[] table = entry.table;
return new SparseNumericDocValues(disi) {
@Override
public long longValue() throws IOException {
return table[(int) values.get(disi.index())];
}
};
} else if (entry.gcd == 1 && entry.minValue == 0) {
return new SparseNumericDocValues(disi) {
@Override
public long longValue() throws IOException {
return values.get(disi.index());
}
};
} else {
final long mul = entry.gcd;
final long delta = entry.minValue;
return new SparseNumericDocValues(disi) {
@Override
public long longValue() throws IOException {
return mul * values.get(disi.index()) + delta;
}
};
}
}
}
}
}