如果有100w的数据,进行分词后,每个id按数字类型进行存储,假设每个行数据都包含相同的词,则每个词的 Posting List 需要占用约4M的空间:
1 int = 4 Bytes
100W int = 400W Bytes ≈ 4M
极大的浪费了空间。则需要对Posting List 进行压缩,压缩算法有:FOR + RBM
FOR算法的核心思想是用减法来削减数值大小,从而达到降低空间存储。
假设V(n)表示数组中第n个字段的值,那么经过FOR算法压缩的数值V(n)=V(n)-V(n-1)。也就是说存储的是后一位减去前一位的差值。存储是也不再按照int来计算了,而是看这个数组的最大值需要占用多少bit来计算,如例所示:
可以看到大小又变小了,但是思考一个问题:是不是还可以进行压缩?是越小越好吗?
是可以再压缩,但是我们还要考虑解码的问题,数据压缩后是要使用的,因此需要解码,压缩得越深,解码越耗时,并且存放容器记录也需要占用空间,因此不是越小越好,那么在哪里取一个平衡,这就是通过计算机动态计算。
- 数组元素值为与前一位的差值V(n)=V(n)-V(n-1),n=2,3,4…
- 稠密数组
- 计算数组中最大值所需占用的大小
- 计算数组是否需要拆分,计算拆分后每组的最大值所需占用的大小并记录
FOR算法的核心是用减法来缩减数值大小,但数值太过离散时,减法能够达到的效果是不明显的,比如100W,200W,300W,相减后是100W,100W,100W,依然很大,这时的压缩效果很不理想,所以引入了RBM算法。
更有效减小数值的大小的方法:除法
RBM的核心就是通过除法来缩减数值大小,但是并不是直接的相除。
比如数组为 [1000, 62101, 131385, 191173, 196658]
其中196658的二进制表示为0000 0000 0000 0011 0000 0000 0011 0010
然后将其高16位和低16位分别转换为10进制:
0000 0000 0000 0011 -> 3
0000 0000 0011 0010 -> 50
那么196658就转换成了(3,50)的表示形式,其效果就相当于除以2^16(因为int类型的最大值就是 2 ^32 = 2^16 * 2^16),商3余50
再因为商和余数都不超过16位,那么我们最大用16bit来存储足够了。也就是short类型,因此商和余数都可以用一个short来盛装。
将每一个商所对应的余数short[]称之为一个容器Container,使用上述所说的short盛装也称为ArrayContainer。
现在有10亿条11位的电话号码,请问如何用2G的空间将他们存储下来?
答:使用数组
存储电话号码phone: arr[(phone-10000000000)/64] | (1 << ((phone-10000000000)%64))
判断电话号码是否存在:arr[(phone-10000000000)/64] & (1 << ((phone-10000000000)%64)) > 0
以上的二维数组,每一个Container中的数据当量足够多时我们认为他是有序连续的,就可以选用bitmap来进行存储,按照规定一个Container的最大值是65534,也就需要65535bit=8k的容器来存储,当然bitmap有个很明显的缺点,那就是无论Container中有多少个数,都要占用8k的大小,所以当数量不超过65535bit /16bit = 4096个时,使用short (16bit)来存储更划算,当每个Container的数量超过4096个时使用bitmap更加划算,那么使用bitmap的Container称为BitmapContainer
还有一个Container叫RunContainer,专门用来解决连续数组存储的,比如[1,2,3,100W],那么可以表示为[1,100W],只存储一个最小值和最大值,如果数组是如下形式
[1,2,3,4,5,100,101,102,999,1000,1001]
就会被拆分为三段:[1,5],[100,102],[999,1001]