BitMap算法及其在lucene中的应用

bitmap应用场景

有一个无序有界int数组{1,2,5,7},初步估计占用内存44=16字节,这倒是没什么奇怪的;但是假如有10亿个这样的数呢,10亿4/(102410241024)=3.72G左右。如果这样的一个大的数据做查找和排序,那估计内存也崩溃了,有人说,这些数据可以不用一次性加载,那就是要存盘了,存盘必然消耗IO。

如果用BitMap思想来解决的话,就好很多。一个byte是占8个bit,如果每一个bit的值就是有或者没有,也就是二进制的0或者1,如果用bit的位置代表数组值有还是没有,那么0代表该数值没有出现过,1代表该数组值出现过。也可以描述数据。具体如下图:

现在假如10亿的数据所需的空间就是3.72G/32,一个占用32bit的数据现在只占用了1bit,节省了不少的空间,排序就更不用说了,一切显得那么顺利。这样的数据之间没有关联性,要是读取的,你可以用多线程的方式去读取。时间复杂度方面也是O(Max/n),其中Max为byte[]数组的大小,n为线程大小

算法实现

第一步: 构建特定长度的byte数组(new byte[capacity / 8 + 1]),其中 capacity为整数数组长度(如:10亿个数字、40亿个数字等);

byte[] bits = new byte[getIndex(n) + 1];

第二步 : 计算数字num在byte[]中的位置(num/8);   

/**
     * num/8得到byte[]的index
     * @param num
     * @return
     */
    public int getIndex(int num){
        return num >> 3;
    }


第三步: 计算数字num在byte[index]的位置(num%8);

 /**
     * num%8得到在byte[index]的位置
     * @param num
     * @return
     */
    public int getPosition(int num){
        return num & 0x07;
    }


第四步: 将所在的位置从0变成1.其他位置不变;

 /**
     * 标记指定数字(num)在bitmap中的值,标记其已经出现过<br/>
     * 将1左移position后,那个位置自然就是1,然后和以前的数据做|,这样,那个位置就替换成1了
     * @param bits
     * @param num
     */
    public void add(byte[] bits, int num){
        bits[getIndex(num)] |= 1 << getPosition(num);
    }

第五步: 判断指定数字num是否已存在;

 /**
     * 判断指定数字num是否存在<br/>
     * 将1左移position后,那个位置自然就是1,然后和以前的数据做&,判断是否为0即可
     * @param bits
     * @param num
     * @return
     */
    public boolean contains(byte[] bits, int num){
        return (bits[getIndex(num)] & 1 << getPosition(num)) != 0;
    }

第六步:重置某一数字对应在bitmap中的值;

 /**
     * 重置某一数字对应在bitmap中的值<br/>
     * 对1进行左移,然后取反,最后与byte[index]作与操作。
     * @param bits
     * @param num
     */
    public void clear(byte[] bits, int num){
        bits[getIndex(num)] &= ~(1 << getPosition(num));
    }

 

算法在lucene合并倒排表中(多should查询求并集)的应用

public void collect(int doc) throws IOException {
      hasMatches = true;
        //doc为文档号, MASK的值为2047,作为理解倒排表合并的原理,我们不用考虑文档号大于2047的情况, 如果大于2047也不会被抛弃,而是重复利用?
      final int i = doc & MASK;
        // 每32篇文档都会记录在matching[idx]数组的同一个元素中
        // 比如说 0~31的文档号就被记录在 matching[0]这个数组元素中
      final int idx = i >>> 6;
        // 用来去重的存储文档号, 二进制表示的数值中,每个为1的bit位的所属第几位就是文档号的值
        // 比如 00...01001(32位二进制), 说明存储了 文档号 0跟3
        // matching在后面遍历中使用,因为我们还要判断每一篇文档出现的次数是否满足minSHouldMatch
        // 那么通过这个matching值就可以从buckets[]数组中以O(1)的复杂度找到每一篇文档出现的次数
      matching[idx] |= 1L << i;
        // 引用bucket对象,buckets[]数组下标是文档号
        // bucket中的freq统计某个文档号出现的次数
        // 这里的buckets是一个已经初始化了2048个大小的bucket数组
      final Bucket bucket = buckets[i];
      bucket.freq++;
      //这里的scorer是TermScorer类型
      //这里可以看出,打分是一个累加的过程
      bucket.score += scorer.score();
    }

猜你喜欢

转载自blog.csdn.net/asdfsadfasdfsa/article/details/88930228
今日推荐