BloomFilter(布隆过滤器) 原理以及Java实现

Bloom Filter 基本原理以及Java实现

Bloom Filter用于检测元素是否存在于某一集合内,其特点在于:结果判断不在该集合的元素一定不在该集合中,判断在该集合内的元素可能并不在该集合内

 

应用场景:

大规模的,这里可以认为是上千万甚至亿条数据集的查重,且对误报不敏感,常用于判断某一条url是否已经被处理过或者是否存在这样一条url在数据集中。

 

基本原理:

1、计算位数组

对一个集合中的元素通过长度为k的散列函数组进行散列,得到k个索引位置,将事先建立的位数组中这k个位置设为1,重复上述过程,直到遍历完集合中所有的元素。

2、判断某一元素是否存在于该集合中

对该元素用同样的散列组进行散列,得到k个索引,检查位数组中的k个索引位置的值是否都为1,若都为1,则该元素可能存在于该集合中,若存在0,则该元素一定不存在于该集合中

 

关于False Positive 的概率计算,位数组长度和散列函数数量的选择问题:

相关的数学证明可以参考:

《BloomFilter概念和原理》- csdn

wiki Bloom Filter

设位数组长度为m,误报概率为p,集合总大小为n,散列函数数量为k,则有以下关系:

Bloom Filter一种Java实现:

 

package com.ryo.algorithm;

/**
 * <strong>布隆过滤器</strong><br>
 * <p>预设的误报概率为0.01。0.01与0.001有不小的性能差距,而且调整为0.001后散列函数数量也要相应的上调才能保持最优<br>
 * 实测中向构造器传入1000万长度的数组,建立该布隆过滤器平均约需要4.0s的时间,建立的位数组长度约为9500万<br>
 * 位数组长度与集合数组长度的比例受期望误报概率p的影响<br>
 * 性能上主要还可以从散列函数的算法上进行优化,大量的散列是主要的耗时操作<br>
 * 在数据量较小的时候布隆过滤器的查找性能不如一般的哈希表。该类没有对当集合元素数量非常小的情况作处理
 * </p>
 * @param <T>   过滤器元素类型
 * @author shiin
 */
public class BloomFilter<T> {
    /**
     * 期望的误报概率<br>
     */
    private static final float p = 0.01f;
    /**
     * 散列函数数量<br>
     * 该参数实际上需要根据其他参数进行调整<br>
     * 但要写出很多个适用于T的效率较高不易冲突的散列函数实际上不是简单的事情<br>
     * 在这里只实现了4个简单的散列函数,并不保证这些函数是最优的<br>
     * 剩余两个索引位置的计算由4个散列函数中挑选两个进行一次再hash得到
     */
    private static final int k = 6;
    /**
     * 过滤器集合数组
     */
    private T[] n;
    /**
     * 散列结果集  由0,1构成
     */
    private int[]  resultSet;
    /**
     * =resultSet.length;
     */
    private int len;


    /**
     * constructor
     * @param arr   过滤器集合元素数组
     */
    public BloomFilter(T[] arr){
        if(arr != null && arr.length > 0){
            n = arr;
            len = (int)(-1.44*arr.length*Math.log(p)/Math.log(2));
            resultSet = new int[len];
            buildBloomFilter();
        }
    }

    /**
     * 检查en是否存在于过滤器集合中
     * @param en    待检查的元素
     * @return  true:可能在也可能不在集合中 / false:一定不在集合中
     */
    public boolean contains(T en){
        int[] index = getIndexGroup(en);
        for(int i : index){
            if(resultSet[i] == 0)
                return false;
        }
        return true;
    }

    /*——————————————————————————private part——————————————————————————————*/

    /**
     * 构建BloomFilter
     */
    private void buildBloomFilter(){
        for(int i=0 ;i<n.length ;i++){
            for(int index : getIndexGroup(n[i])){
                if(resultSet[index] == 0)
                    resultSet[index] = 1;
            }
        }
    }

    /**
     * 散列函数组<br>
     * 常量k = 6
     * @return 返回T en多个散列后得到的索引
     */
    private int[] getIndexGroup(T en){
        int[] index = new int[k];
        int baseCode = baseHash(en);
        index[0] = hash1(baseCode);
        index[1] = hash2(baseCode);
        index[2] = hash3(baseCode);
        index[3] = hash4(baseCode);
        index[4] = hash2(index[1]);
        index[5] = hash2(index[2]);
        return index;
    }

    /*————————————————————————————散列函数集——————————————————————————该部分有较大的优化空间*/
    private int baseHash(T en){
        int baseCode;
        if((baseCode = en.hashCode()) < 0)
            baseCode = -baseCode;
        return baseCode;
    }

    /**
     * 直接取余
     */
    private int hash1(int baseCode){
        return baseCode%len;
    }

    /**
     * 32bit Mix Functions
     */
    private int hash2(int baseCode){
        int h = baseCode+~(baseCode<<15);
        h ^= (h>>10);
        h += (h<<3);
        h ^= (h>>6);
        h += ~(h<<11);
        h ^= (h>>16);
        if(h < 0)
            h = -h;
        return h%len;
    }

    /**
     * 乘以自然常数e取其小数部分乘以len再取整数部分即得到索引<br>
     * 该hash算法计算效率较低
     */
    private int hash3(int baseCode){
        double h = baseCode*Math.E;
        return (int)((h-(long)h)*len);
    }

    /**
     * 利用String提供的散列方法
     */
    private int hash4(int baseCode){
        String str = baseCode+""+~baseCode;
        int h = str.hashCode();
        if(h < 0)
            h = -h;
        return h%len;
    }
}


猜你喜欢

转载自blog.csdn.net/my_dearest_/article/details/80139748