布隆过滤器

布隆过滤器

一、定义

​ 布隆过滤器:1970年由布隆提出,它是一个空间效率高的概率型数据结构,可以用来告诉你:一个元素一定存在或者可能存在。
优缺点:
​ 优点:空间效率和查询时间都远远超过一般的算法。
​ 缺点:有一定的误判率,删除困难。
本质是:一个很长的二进制向量和一系列随机映射函数(Hash函数)

二、应用场景

  • 网页黑名单系统,垃圾邮件过滤系统,爬虫的网址判重系统,解决缓存穿透问题。
  • 解决缓存穿透问题的原理:将所有key值存储到布隆过滤器中,请求到达先经过布隆过滤器,布隆过滤器说数据不存在,则一定不存在,返回不存在即可。布隆过滤器说存在,则可能存在,去数据库查询即可。可通过布隆过滤器,解决缓存穿透的问题。

三、布隆过滤器原理

	* 假设布隆过滤器由20位二进制,3个哈希函数组成,每个元素经过哈希函数处理都能生成一个索引位置。
	* 添加元素:将每个哈希函数生成的索引位置都设为一。
  • 查询元素是否存在
    果有一个哈希函数生成的索引位置不为1,这代表不存在(100%);
    如果每一个哈希函数生成的索引位置都为1,就代表存在(存在误判);
    * 原理图

image-20210625190836216

四、时间空间复杂度

  • 添加查询的时间复杂度都是:0(k),k是哈希函数的个数。空间复杂度是:O(m),m是二进制的个数。

五、正确性证明

  • 布隆过滤器的正确性,由严格的数学证明,具体可参考相关文献,这里重在应用。
  • 下面给出结论:p误判率、m二进制位的个数、k哈希函数的个数、n数据规模。

image-20210625190905520

六:代码实现

/**
 * @author 韩俊强
 * @Description 布隆过滤器
 * @createTime 2021年06月23日 10:20:00
 */
public class BloomFilter <T>{
    // 二进制向量的长度(一共有多少位二进制)
    private int bitSize;
    // 二进制向量
    private long[] bits;
    // 哈希函数的个数
    private int hashSize;

    /**
     * 构造函数
     * @param n 数据规模
     * @param p 误判率
     * @author 韩俊强
     * @date: 2021/6/23 10:24
     */
    public BloomFilter(int n, double p) {
        if (n <= 0 || p <= 0 || p >= 1){
            throw  new IllegalArgumentException("wrong n or p");
        }
        // 套用公式计算 bitSize hashSize
        double ln2 = Math.log(2);
        bitSize = (int)(-(n * Math.log(p))/(ln2 * ln2));
        hashSize =(int)(bitSize * ln2 /n);
        // bits数组的长度
        bits = new long[(bitSize + Long.SIZE - 1)/ Long.SIZE];
    }

    /**
     * 添加元素
     * @param 要添加的元素
     * @return:bits是否发送了修改
     * @author 韩俊强
     */
    public boolean put(T value){
        nullCheck(value);
        // 具体怎么实现的求索引,可用现有实现。这里是测试,不具备生产环境使用。
        int hash1 = value.hashCode( ) ;
        int hash2 = hash1 >>> 16;

        boolean result = false;
        for (int i = 1; i <= hashSize; i++) {
            int combinedHash = hash1 + (i * hash2) ;
            if ( combinedHash < 0) {
                combinedHash = ~ combinedHash;
            }
            // 生成一个二进制索引
            int index = combinedHash % bitSize;
            if (set(index)) {
                result = true;
            }
        }
        return result;
    }

    /**
     * 判断元素是否存在
     * @param value
     * @return:是否包含元素
     * @author 韩俊强
     */
    public boolean contains( T value){
        nullCheck(value);
        // 具体怎么实现的求索引,可用现有实现。这里是测试,不具备生产环境使用。
        int hash1 = value.hashCode( ) ;
        int hash2 = hash1 >>> 16;
        for (int i = 1; i <= hashSize; i++) {
            int combinedHash = hash1 + (i * hash2) ;
            if ( combinedHash < 0) {
                combinedHash = ~ combinedHash;
            }
            // 生成一个二进制索引
            int index = combinedHash % bitSize;
            if (!get(index)){
                return false;
            }
        }
        return true;
    }

    /**
     *
     * @param index
     * @return:是否发送修改
     * @author 韩俊强
     */
    private boolean set(int index){
        long value = bits[index/Long.SIZE];
        int bitValue = 1 << (index % Long.SIZE);
        bits[index/Long.SIZE] = value | bitValue;
        return (value & bitValue) == 0;
    }

    /**
     *
     * @param index
     * @return:是否为1
     * @author 韩俊强
     */
    private boolean get(int index){
        long value = bits[index/Long.SIZE];
        return (value = value & (1 << (index % Long.SIZE))) != 0;

    }

    private void nullCheck(T value){
        if (value == null){
            throw  new IllegalArgumentException("value must not be null");
        }
    }
    // 不提供删除功能,不适合。强行实现思路,可以使用计数的方式,删除则对应位--;
}

posted @ 2021-06-23 14:56  浅蓝色丶  阅读(92)  评论(1编辑  收藏  举报