【数据结构】布隆过滤器

布隆过滤器

简介
  • 介绍:布隆过滤器是一种比较巧妙的概率型数据结构,是一个bit向量或者说是一个bit数组。用于判断一个元素是否在计划中。
  • 特点:可以高效的插入和查询,不支持删除。查询结果:元素一定不存在,或者可能存在。
  • 比较:优点:相对与传统的list,set,map等数据结构,更高效和占用空间更少;缺点:返回结果具有概率性,不确定。
算法描述(不写上PPT,用于描述)
  1. 增:为了添加一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
  2. 删:不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。
  3. 查:为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。

当add的元素过多时,即n/m在这里插入代码片过大时(n是元素数,m是bloom filter的bits数),会导致false positive过高,此时就需要重新组建filter,但这种情况相对少见。
注意:如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。

如何选择哈希值和布隆过滤器长度?

在这里插入图片描述
在这里插入图片描述

	k 为哈希函数个数,m 为布隆过滤器长度,n 为插入的元素个数,p 为误报率。
实际用处
  • 减少磁盘IO
  • 网络请求
  • 垃圾邮件过滤的黑白名单方法
  • 爬虫的网址判重
  • 防止缓存击穿
哈希函数选择

选择性能较高的哈希函数,推荐 MurmurHash、Fnv 这些。
当k很大时,设计k个独立的hash function是不现实并且困难的。对于一个输出范围很大的hash function(例如MD5产生的128 bits数),如果不同bit位的相关性很小,则可把此输出分割为k份。或者可将k个不同的初始值(例如0,1,2, … ,k-1)结合元素,feed给一个hash function从而产生k个不同的数。

大Value拆分

Redis 因其支持 setbit 和 getbit 操作,且纯内存性能高等特点,因此天然就可以作为布隆过滤器来使用。但是布隆过滤器的不当使用极易产生大 Value,增加 Redis 阻塞风险,因此生成环境中建议对体积庞大的布隆过滤器进行拆分。

拆分的形式方法多种多样,但是本质是不要将 Hash(Key) 之后的请求分散在多个节点的多个小 bitmap (本质是Redis中的string)上,而是应该拆分成多个小 bitmap 之后,对一个 Key 的所有哈希函数都落在这一个小 bitmap 上。

使用例子

guava实现布隆过滤器
  1. 添加依赖
		<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>22.0</version>
        </dependency>
  1. 测试demo
public class TestDemo {
    
    // 初始化大小
    private static int size = 1000000;
    // 创建布隆过滤器
    private static BloomFilter<Integer> bloomFilter = BloomFilter.create(Funnels.integerFunnel(), size);
    
    public static void main(String[] args) {
        // 初始化布隆过滤器
        for (int i = 0; i < size; i++) {
            bloomFilter.put(i);
        }
        
        // 已经存在过滤器中的数据判断
        for (int i = 0; i < size; i++) {
            if (!bloomFilter.mightContain(i)) {
                System.out.println("存在的是否会误报不存在。");
            }
        }
        
        // 误判测试
        List<Integer> list = new ArrayList<Integer>(1000);
        for (int i = size + 10000; i < size + 20000; i++) {
            if (bloomFilter.mightContain(i)) {
                list.add(i);
            }
        }
        System.out.println("有误伤的数量:" + list.size());
    }
}
redis实现布隆过滤器
  1. 引入依赖
		<dependency>
            <groupId>com.redislabs</groupId>
            <artifactId>jrebloom</artifactId>
            <version>1.0.1</version>
        </dependency>

测试代码

import redis.clients.jedis.Jedis;
import redis.clients.jedis.util.Hashing;

public class RedisMain {
    static final int expectedInsertions = 1000;//要插入多少数据
    static final double fpp = 0.01;//期望的误判率

    //bit数组长度
    private static long numBits;

    //hash函数数量
    private static int numHashFunctions;

    static {
        numBits = optimalNumOfBits(expectedInsertions, fpp);
        numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        for (int i = 0; i < 1000; i++) {
            long[] indexArray = getIndexArray(String.valueOf(i));
            for (long index : indexArray) {
                jedis.setbit("codebear:bloom", index, true);
            }
        }
        int num = 0;
        for (int i = 1000; i < 2000; i++) {
            long[] indexArray = getIndexArray(String.valueOf(i));
            for (long index : indexArray) {
                if (!jedis.getbit("codebear:bloom", index)) {
                    System.out.println(i + "一定不存在");
                    num++;
                    break;
                }
            }
        }
        System.out.println("一定不存在的有" + num + "个");
    }

    /**
     * 根据key获取bitmap下标
     */
    private static long[] getIndexArray(String key) {
        long hash1 = hash(key);
        long hash2 = hash1 >>> 16;
        long[] result = new long[numHashFunctions];
        for (int i = 0; i < numHashFunctions; i++) {
            long combinedHash = hash1 + i * hash2;
            if (combinedHash < 0) {
                combinedHash = ~combinedHash;
            }
            result[i] = combinedHash % numBits;
        }
        return result;
    }

    private static long hash(String key) {
        return Hashing.MURMUR_HASH.hash(key);
    }

    //计算hash函数个数
    private static int optimalNumOfHashFunctions(long n, long m) {
        return Math.max(1, (int) Math.round((double) m / n * Math.log(2)));
    }

    //计算bit数组长度
    private static long optimalNumOfBits(long n, double p) {
        if (p == 0) {
            p = Double.MIN_VALUE;
        }
        return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));
    }
}

博客https://www.cnblogs.com/CodeBear/p/10911177.html

又有另外一种说法:

之前的布隆过滤器可以使用Redis中的位图操作实现,直到Redis4.0版本提供了插件功能,Redis官方提供的布隆过滤器才正式登场。布隆过滤器作为一个插件加载到Redis Server中,就会给Redis提供了强大的布隆去重功能。
博客https://www.cnblogs.com/heihaozi/p/12174478.html

问题:

  1. 什么时候初始化布隆过滤器数据?

在使用过滤器之前初始化过滤器。

  1. 如果一直有一个非法请求到后台数据库,当如何处理?

在缓存中设置一个null的值,如果是非法请求,则返回null。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值