lucene中的docValue实现源码解读(四)——BinaryDocValue的写入

阅读更多

BinaryDocValue是存储的byte[],也就是他可以存一些字符串、图片,等可以用byte[]表示的内容。他的使用场景我们不关心,主要看下他是如何在lucene中存储的吧。他的添加还是在DefaultIndexingChain.indexDocValue方法里面,这里还是先保存在内存中,我们介绍一下如何在内存中保存的。使用的类是:BinaryDocValuesWriter,构造方法如下:

public BinaryDocValuesWriter(FieldInfo fieldInfo, Counter iwBytesUsed) {
	this.fieldInfo = fieldInfo;//要添加的域
	this.bytes = new PagedBytes(BLOCK_BITS);//这个是要保存所有的byte[]的容器,他的好处是他是压缩的,可以节省内存。可以直接将其看作是一个很大的byte[]
	this.bytesOut = bytes.getDataOutput();//获得上面说的byte[]的添加入口
	this.lengths = PackedLongValues.deltaPackedBuilder(PackedInts.COMPACT);//用来记录每个doc的byte[]长度的东西,因为在最后是将每个doc的byte[]放到一个大大的byte[]里面,所以要记录每个doc的byte[]的长度
	this.iwBytesUsed = iwBytesUsed;//用来记录使用的内存的,可以忽略
	this.docsWithField = new FixedBitSet(64);//记录含有值得doc的id的对象
	this.bytesUsed = docsWithFieldBytesUsed();
	iwBytesUsed.addAndGet(bytesUsed);
}

 下面看下添加docValue的方法:

public void addValue(int docID, BytesRef value) {
	。。。//校验的方法去掉
	// Fill in any holes:
	while (addedValues < docID) {//这个是添加窟窿,因为有的doc没有byte[],这时候要在记录长度的对象里面用0填充。(其实我觉得没有必要,只需要稍微改动下下面的迭代器的代码就可以避免这里的操作了。见下面加粗倾斜的说明)
		addedValues++;
		lengths.add(0);
	}
	addedValues++;
	lengths.add(value.length);//记录要添加的byte[]的大小
	try {
		bytesOut.writeBytes(value.bytes, value.offset, value.length);//将byte[]写入到内存中
	} catch (IOException ioe) {
		// Should never happen!
		throw new RuntimeException(ioe);
	}
	docsWithField = FixedBitSet.ensureCapacity(docsWithField, docID);
	docsWithField.set(docID);//记录这个id,表示其在这个域中有值
	updateBytesUsed();
}

 通过上面的额方法已经将每一个doc的byte[]写入到内存里面了,我们看下当flush的时候的操作

public void flush(SegmentWriteState state, DocValuesConsumer dvConsumer) throws IOException {
	final int maxDoc = state.segmentInfo.getDocCount();//当前段的所有的doc的数量
	bytes.freeze(false);
	final PackedLongValues lengths = this.lengths.build();//
	dvConsumer.addBinaryField(fieldInfo, new Iterable() {//添加的一个参数是一个迭代器生成器,重写了iterator方法,返回的迭代器的参数第一个是所有的额doc的数量,第二个是记录每个doc的byte[]的对象
		public Iterator iterator() {
			return new BytesIterator(maxDoc, lengths);
		}
	});
}

 看一下迭代器,BytesIterator的next方法,他返回所有的要保存在索引中的byte[]

public BytesRef next() {
	if (!hasNext()) {
		throw new NoSuchElementException();
	}
	final BytesRef v;
	if (upto < size) {
		int length = (int) lengthsIterator.next();//得到这个byte[]de 长度
		value.grow(length);
		value.setLength(length);
		try {
			bytesIterator.readBytes(value.bytes(), 0, value.length());//从里面读取指定长度的byte[],读取到value里面
		} catch (IOException ioe) {
			// Should never happen!
			throw new RuntimeException(ioe);
		}
		if (docsWithField.get(upto)) {//如果存在这个id的,则返回value。(如果我们把这个检查放在lengthsIterator.next前面,如果不存在的话,就可以直接返回null,这样上面也就不用填窟窿了)
			v = value.get();
		} else {//不存在返回null
			v = null;
		}
	} else {
		v = null;
	}
	upto++;
	return v;
}

 从这个方法里面可以得出,他会将所有的doc的值返回,如果这个doc没有值,则返回null,堆byte[]的读取是通过记录每个byte[]的对象 以及记录所有的byte[]的对象联合实现的。我们看下最终的flush方法,也就是DocValueConsumer使用迭代器的方法。4.10.4中使用的是Lucene410DocValuesConsumer,和数字类型的docValue是一样的,只不过调用的方法是addBinaryField方法,

@Override
public void addBinaryField(FieldInfo field, Iterable values) throws IOException {
	meta.writeVInt(field.number);//写入域号,这里的meta和numericDocValue的是一样的,都是data的索引文件,
	meta.writeByte(Lucene410DocValuesFormat.BINARY);
	int minLength = Integer.MAX_VALUE;
	int maxLength = Integer.MIN_VALUE;
	final long startFP = data.getFilePointer();
	long count = 0;
	boolean missing = false;
	for (BytesRef v : values) {//循环所有的byte[],如果不是null,则写入到data中,
		final int length;
		if (v == null) {
			length = 0;
			missing = true;
		} else {
			length = v.length;
		}
		minLength = Math.min(minLength, length);
		maxLength = Math.max(maxLength, length);
		if (v != null) {
			data.writeBytes(v.bytes, v.offset, v.length);//写入所有的byte[]到data中,这样data就相当于是一个大大的byte[]了,将多个晓得byte[]记录在里面。
		}
		count++;
	}
	
	//写入的格式,有两种,一个是没有压缩的,当所有的byte[]一样长的时候,否则使用压缩的。
	meta.writeVInt(minLength == maxLength ? BINARY_FIXED_UNCOMPRESSED : BINARY_VARIABLE_UNCOMPRESSED);
	if (missing) {//如果有没有值得doc,则在data中记录所有的含有值的id,这一点和numericDocValue也是一样的。
		meta.writeLong(data.getFilePointer());//在meta中记录docset写入的fp,也就是索引。
		writeMissingBitset(values);
	} else {//否则写入-1
		meta.writeLong(-1L);
	}
	meta.writeVInt(minLength);
	meta.writeVInt(maxLength);
	meta.writeVLong(count);//doc的数量,这个源码中的注释错了,他的注释是写入的值得个数,并不是的,其实是doc的数量,因为有的额doc是没有值的。
	meta.writeLong(startFP);//记录没有写入任何的byte[]是的data的fp,

	// if minLength == maxLength, its a fixed-length byte[], we are done (the addresses are implicit) otherwise, we need to record the length fields...  这句英文的意思是如果所有的byte[]的长度都一样,则就没事了,但是这个在实际中几乎是不成立的,
	if (minLength != maxLength) {//如果长度不一致,则写入每个doc的值得长度,这样就很容易从那个大大byte[]里面里面找到每个doc的开始位置了。
		meta.writeLong(data.getFilePointer());//记录此时的data的索引,下面要记录每个doc的开始位置了
		meta.writeVInt(PackedInts.VERSION_CURRENT);
		meta.writeVInt(BLOCK_SIZE);

		final MonotonicBlockPackedWriter writer = new MonotonicBlockPackedWriter(data, BLOCK_SIZE);
		long addr = 0;
		writer.add(addr);
		for (BytesRef v : values) {
			if (v != null) {//写入每个doc的开始位置,这样,如果这个doc有值的话,则下一个doc的开始位置和这个doc的开始位置是不一样的,中间的值就是这个doc的值
				addr += v.length;
			}
			writer.add(addr);
		}
		writer.finish();
	}
}

 这样,就将每个byte[]写入到索引里面了。

总结一下,BinaryDocValue其实就是将所有的byte[]写入到硬盘上,然后再将记录每个doc的byte[]长度的数字也写到硬盘上,并且将每个部分的fp(也就是开始位置,理解为索引)写入到meta文件中。

其实binaryDocValue要比NumericDocValue简单,因为他不会有很多形式。

 

 

 

 

 

你可能感兴趣的:(docValue,BinaryDocValue,存储格式)