去重编码是Lucene中对int类型数据的一种压缩存储方式,在FacetsConfig类中用到此方法来处理int类型数据。其优点在于,存储一个原本需要固定4个字节空间大小的int类型的数据,最好的情况下只要1个字节,最差的情况下需要5个字节。
处理过程
去重编码的过程主要分三步:
- 排序
- 去重
- 差值存储
关系图
根据int数值的大小,在去重编码后存储该数值所需要的字节大小关系图如下
数值范围(指数) |
数值范围(10进制) |
字节大小 |
0 ~ (2^7 - 1) |
0 ~ 127 |
1 |
2^7 ~ (2^14 - 1) |
128 ~ 16383 |
2 |
2^14 ~ (2^21 - 1) |
16384 ~ 2097151 |
3 |
2^21 ~ (2^28 - 1) |
2097152 ~ 268435455 |
4 |
2^28 ~ * |
268435456 ~ * |
5 |
去重编码中最重要的一点是差值存储,从上图可以看出,我们在存储一组有序的数值时,除第一个数值外,其他的数值如果只存储跟它前面数值的差值,那么可以使得达到最大的压缩比。这种方式在存储大数值时的有点更明显。 |
|
|
1
| 例如我们有一组数据:{17832,17842,17844},如果我们直接对3个数值进行存储(不存储差值),那么最终需要9个字节才能存储这三个数值,而如果我们进行差值存储,那么我们需要存储的数据就变为: {17832,10,2},其中10是17842跟17832的差值,2是17844跟17842的差值,那么最终只需要5个字节存储即可。
|
去重编码源码
相比较源码,删除了代码中的IntsRef类,便于理解
encode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public class ConvertIntToByteRef { public static BytesRef dedupAndEncode(int[] ordinals) { Arrays.sort(ordinals, 0, ordinals.length); byte[] bytes = new byte[5*ordinals.length]; int lastOrd = -1; int upto = 0; for(int i=0;i<ordinals.length;i++) { int ord = ordinals[i]; if (ord > lastOrd) { int delta; if (lastOrd == -1) { delta = ord; } else { delta = ord - lastOrd; } if ((delta & ~0x7F) == 0) { bytes[upto] = (byte) delta; upto++; } else if ((delta & ~0x3FFF) == 0) { bytes[upto] = (byte) (0x80 | ((delta & 0x3F80) >> 7)); bytes[upto + 1] = (byte) (delta & 0x7F); upto += 2; } else if ((delta & ~0x1FFFFF) == 0) { bytes[upto] = (byte) (0x80 | ((delta & 0x1FC000) >> 14)); bytes[upto + 1] = (byte) (0x80 | ((delta & 0x3F80) >> 7)); bytes[upto + 2] = (byte) (delta & 0x7F); upto += 3; } else if ((delta & ~0xFFFFFFF) == 0) { bytes[upto] = (byte) (0x80 | ((delta & 0xFE00000) >> 21)); bytes[upto + 1] = (byte) (0x80 | ((delta & 0x1FC000) >> 14)); bytes[upto + 2] = (byte) (0x80 | ((delta & 0x3F80) >> 7)); bytes[upto + 3] = (byte) (delta & 0x7F); upto += 4; } else { bytes[upto] = (byte) (0x80 | ((delta & 0xF0000000) >> 28)); bytes[upto + 1] = (byte) (0x80 | ((delta & 0xFE00000) >> 21)); bytes[upto + 2] = (byte) (0x80 | ((delta & 0x1FC000) >> 14)); bytes[upto + 3] = (byte) (0x80 | ((delta & 0x3F80) >> 7)); bytes[upto + 4] = (byte) (delta & 0x7F); upto += 5; } lastOrd = ord; } } return new BytesRef(bytes, 0, upto); }
public static void main(String[] args) { int[] array = {3, 2, 2, 8, 12}; BytesRef ref = ConvertIntToByteRef.dedupAndEncode(array); System.out.println(ref.toString()); } }
|
最终结果用BytesRef对象表示,上面的输出结果:[2 1 5 4]
decode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class ConvertByteRefToInt { public static void decode(BytesRef bytesRef){ byte[] bytes = bytesRef.bytes; int end = bytesRef.offset + bytesRef.length; int ord = 0; int offset = bytesRef.offset; int prev = 0; while (offset < end) { byte b = bytes[offset++]; if (b >= 0) { prev = ord = ((ord << 7) | b) + prev; System.out.println(ord); ord = 0; } else { ord = (ord << 7) | (b & 0x7F); } } } public static void main(String[] args) { int[] array = {3, 2, 2, 8, 12}; BytesRef ref = ConvertIntToByteRef.dedupAndEncode(array); ConvertByteRefToInt.decode(ref); } }
|
结语
去重编码(dedupAndEncode)是Lucene中的压缩存储的方式之一,还有VInt,VLong等数据类型都是属于压缩存储,在后面的博客中会一一介绍。demo看这里:https://github.com/luxugang/Lucene-7.5.0/tree/master/LuceneDemo/src/main/java/lucene/compress/dedupAndEncodeTest