BinaryDocValue的读取和NumericDocValue的读取的原理是差不多的,都是在打开一个新的段时,先读取meta文件,也就是 索引文件,读取到内存,然后保存起来,再用刀某个doc的docValue的时候再去读data文件。看下读取meta文件的代码吧,是在Lucene410DocValuesProducer.readBinaryEntry(IndexInput)中:
static BinaryEntry readBinaryEntry(IndexInput meta) throws IOException { BinaryEntry entry = new BinaryEntry(); entry.format = meta.readVInt();//存储的格式 entry.missingOffset = meta.readLong();//记录那些含有值的docSet的fp entry.minLength = meta.readVInt();//byte[]长度的最小值 entry.maxLength = meta.readVInt();//byte[]长度的最大值 entry.count = meta.readVLong();//所有的doc的数量 entry.offset = meta.readLong();//真正存储docValue的部分的fp switch (entry.format) { case BINARY_FIXED_UNCOMPRESSED://没有压缩的,说明是长度一致的,直接根据id * minLength找到开始位置。 break; case BINARY_PREFIX_COMPRESSED://没有使用! entry.addressesOffset = meta.readLong(); entry.packedIntsVersion = meta.readVInt(); entry.blockSize = meta.readVInt(); entry.reverseIndexOffset = meta.readLong(); break; case BINARY_VARIABLE_UNCOMPRESSED: entry.addressesOffset = meta.readLong();//这个是记录每个doc的byte[]的长度的那一块的fp。 entry.packedIntsVersion = meta.readVInt(); entry.blockSize = meta.readVInt();//这个是读取一个块的大小,因为在存储那些数子的时候,是按照块来存储的。 break; default: throw new CorruptIndexException("Unknown format: " + entry.format + ", input=" + meta); } return entry; }
这样读取了meta文件,然后保存在内存中,我们看下具体怎么读取每个doc的docValue的吧:代码在Lucene410DocValuesProducer.getBinary(FieldInfo)中,
public BinaryDocValues getBinary(FieldInfo field) throws IOException { BinaryEntry bytes = binaries.get(field.number); switch (bytes.format) { case BINARY_FIXED_UNCOMPRESSED: return getFixedBinary(field, bytes);//所有的byte[]长度一致的格式 case BINARY_VARIABLE_UNCOMPRESSED: return getVariableBinary(field, bytes);//byte[]长度不一样的格式 case BINARY_PREFIX_COMPRESSED://没有用刀 return getCompressedBinary(field, bytes); default: throw new AssertionError(); } }
挨个看看,先看byte[]长度都一样的情况:
private BinaryDocValues getFixedBinary(FieldInfo field, final BinaryEntry bytes) throws IOException { // 因为已经知道了每个byte[]的长度,所以直接乘以数量,此时不会有missing的,即所有的doc都含有值,所以count就是值得个数 final IndexInput data = this.data.slice("fixed-binary", bytes.offset, bytes.count * bytes.maxLength);//从索引中获得指定位置的数据 final BytesRef term = new BytesRef(bytes.maxLength); final byte[] buffer = term.bytes;//读取到这个里面来 final int length = term.length = bytes.maxLength; return new LongBinaryDocValues() { @Override public BytesRef get(long id) { try { data.seek(id * length);//找到开始的位置 data.readBytes(buffer, 0, buffer.length);//读取一个byte[] return term; } catch (IOException e) { throw new RuntimeException(e); } } }; }
可以发现,如果 byte[]的长度都一样,是很容易理解的。直接用id*每个byte[]的长度即可,而且byte[]的长度是已知的,因为最大值和最小值都保存了。再看下不一样长的:
private BinaryDocValues getVariableBinary(FieldInfo field, final BinaryEntry bytes) throws IOException { //读取位置,因为当byte[]不一样的时候,会保存每个doc的byte[]的长度,这里就是将每个doc的长度读取出来(其实是按照块读取的,并不是每个doc都读取出来,不过我们不研究那个,这里假设是都读取出来了,或者是提供的功能能够得到每个doc的byte[]的开始位置) final MonotonicBlockPackedReader addresses = getAddressInstance(field, bytes); //将所有的值读取出来(也就是所有的doc的byte[]) final IndexInput data = this.data.slice("var-binary", bytes.offset, bytes.addressesOffset - bytes.offset); final BytesRef term = new BytesRef(Math.max(0, bytes.maxLength)); final byte buffer[] = term.bytes; return new LongBinaryDocValues() { @Override public BytesRef get(long id) { long startAddress = addresses.get(id);//当前doc的开始位置 long endAddress = addresses.get(id + 1);//当前doc的结束位置 int length = (int) (endAddress - startAddress);//长度,如果一个doc没有值,那么他的下一个doc的开始位置他的开始位置是一样的,则这个是0 try { data.seek(startAddress); data.readBytes(buffer, 0, length); term.length = length; return term;//返回读取到的byte[]. } catch (IOException e) { throw new RuntimeException(e); } } }; }
这样也就看完了长度不一致的读取,在读取的时候,会根据id得到开始位置, 然后再从那个大大的byte[]中根据偏移量读取。他的效率不如长度一致的byte[]的高,因为要读取两次,而长度一致的只需要读取一次。还有需要注意的就是,任何一个doc都会读取到一个BytesRef,所以要看那个记录有没有值的bitset才能决定这个doc有没有binaryDocValue。