数据结构与算法之布隆过滤器(bloom filter)

数据结构与算法之布隆过滤器(bloom filter)

1. 布隆过滤器是什么

  1. 1970年由布隆提出的一种快速判断数据是否存在的算法。它实际上是由一个很长的二进制向量和一系列随机映射函数组成。
  2. 简单来说,先建立一个很大的bit数组,然后将数据如字符串通过随机映射函数(简单如hashcode()方法)生成正整数,然后将这个正整数作为index,如果数据存在就将这个index所对应的数组中标记为设置为1,不存在就设置为0. 这样下次查询时,直接将需要查询的数据通过随机映射函数生成一个index,然后查询bit数组中对应index的值是否为0,为0,肯定不存在。为1,很大可能存在。
  3. 上述说的,很大可能存在是因为各种映射算法如hashcode()算法,很可能产生碰撞。例如2个不同字符串得出的hashcode()整数可能相同。
  4. 上述的hashcode()算法,可能得出的是负数,这时候一般通过将其与最大正整数 进行与(&)运算,这样整数符号位是0,负数符号位是1,相与之后,就是0,变成正数。
  • 下述是简单布隆过滤器示意图,注意只做了一次hashcode()映射,哈希碰撞几率较高。
    在这里插入图片描述
  • 下述是针对一个输入值做了三次哈希碰撞,也就是一个bit数组中,每个值都会生成三个hashcode作为bit数组下标,并且标记为1.这样就可以降低哈希碰撞造成的影响。
  • 注意,哈希碰撞除了映射算法之外,存放哈希的数组也需要足够大。按照一个字节byte是8bit,1MB=810241024 bit,10MB一般已经足够存放一般场景下的标记数据了。
    在这里插入图片描述

2. 布隆过滤器解决了什么问题

  1. 在生产中,如何快速查找数据是经常遇到的问题。例如常说的,mysql使用中,读取数据和写数据占比有时候会64开甚至73开。也就是读取数据一般比写入数据要频繁很多。读取数据中很多时候需要快速判断一个值是否在一个集合中。这时候如何快速找出并且时间和空间复杂度都较好
  2. 布隆过滤器就是在时间复杂度和空间复杂度都很优越的一种快速判断是否存在的算法。可以很快判断是否不存在,可以很快判断存在(很大几率存在,由于哈希碰撞或者其他映射函数无法避免碰撞问题,所以无法绝对判定是否存在)
  3. 实际生产中还会有如二分查找、哈希map等方法来进行快速查找。但布隆过滤器有其特有的优越性

3. 布隆过滤器怎么使用

3.1 环境准备

  1. idea 2020
  2. maven环境,我的是3.6.3,安装maven
  3. jdk 1.8,安装jdk

3.2 idea中创建maven项目

  1. 导入谷歌的guava jar包
    https://mvnrepository.com/search?q=guava
    在这里插入图片描述
    在这里插入图片描述

  2. pom文件

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>

  1. java 代码
public class GuavaTest {
    private static final int insertions = 1000000;//100w

    public static void main(String[] args) {

        /*
        * Creates a {@link BloomFilter} with the expected number of insertions and expected false
         * positive probability. 创建一个预期插入次数和预期误判可能性的布隆过滤器
         *
         * <p>Note that overflowing a {@code BloomFilter} with significantly more elements than specified,
         * will result in its saturation, and a sharp deterioration of its false positive probability.
         *注意,如果写入的数据超出之前设定的插入数字限定会导致数据穿透,并且导致误判率的急剧下降。
         * 这就是之前提的,因为本身是一个bit数组,如果存入的数据过多,生成的index碰撞几率就会变大。
         *
         * <p>The constructed {@code BloomFilter} will be serializable if the provided {@code Funnel<T>}
         * is.
         *
         * <p>It is recommended that the funnel be implemented as a Java enum. This has the benefit of
         * ensuring proper serialization and deserialization, which is important since {@link #equals}
         * also relies on object identity of funnels.
         *
         * @param funnel the funnel of T's that the constructed {@code BloomFilter} will use
         * @param expectedInsertions the number of expected insertions to the constructed {@code
         *     BloomFilter}; must be positive
         * @param fpp the desired false positive probability (must be positive and less than 1.0)
         * @return a {@code BloomFilter}
        * */

        // 代码参考自 博客 http://geetry.com/2018/03/29/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E4%BB%8B%E7%BB%8D%E5%92%8C%E5%BA%94%E7%94%A8/#more

        //初始化一个存储String数据的布隆过滤器,初始化大小为100w
        BloomFilter<String> bloomFilter =
                BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), insertions,0.01);

        //初始化一个存储String数据的set,初始化大小为100w,做验证参考
        Set<String> sets = new HashSet<String>(insertions);

        //初始化一个存储String数据额list,初始化大小为100w
        List<String> lists = new ArrayList<String>(insertions);

        //向三个容器中初始化100w个随机唯一的字符串
        for (int i = 0; i < insertions; i++) {
            String uuid = UUID.randomUUID().toString();

            // 随机生产的字符串,加入到布隆过滤器中
            bloomFilter.put(uuid);

            // 加入到set集合中,这样可以去重
            sets.add(uuid);

            // 加入到list中,这个对比set不保证去重
            lists.add(uuid);
        }

        //布隆过滤器误判的次数
        int wrongCount = 0;

        //布隆过滤器正确地次数
        int rightCount = 0;


        //随机抽取1w数据做验证
        for (int i = 0; i < 10000; i++) {
            // 创建一个字符串作为输入字符串来和布隆过滤器中数据比对
            // 如果i求余100是0,就取i/100序号下对应lists中字符串作为输入值,否则临时创建一个uuid字符串
            String test = i % 100 == 0 ? lists.get(i / 100) : UUID.randomUUID().toString();

            //布隆过滤器验证通过
            if (bloomFilter.mightContain(test)) {
                // 如果set中也存在,那就确实存在,因为set中存储数据肯定是去重后的,并且数据和布隆过滤器是一批添加的
                if (sets.contains(test)) {
                    rightCount++;
                } else {
                    wrongCount++;
                }
            }
        }

        System.out.println("right count : " + rightCount);
        System.out.println("wrong count : "+wrongCount);
        System.out.println("wrong rate : "+Math.round(((wrongCount*1.0)/(9900))*100)+"%");
    }
}

4.布隆过滤器的应用

  1. redis中添加布隆过滤器做数据存在的快速判定,https://github.com/RedisBloom/RedisBloom
  2. hbase中数据读取时,快速从本地hfile判定是否存在对应rowkey,http://hbase.apache.org/2.2/book.html在这里插入图片描述
  3. 数据库防止穿库。 Google Bigtable,HBase 和 Cassandra 以及 Postgresql 使用BloomFilter来减少不存在的行或列的磁盘查找。避免代价高昂的磁盘查找会大大提高数据库查询操作的性能。
  4. 业务场景中判断用户是否阅读过某视频或文章,比如抖音或头条,当然会导致一定的误判,但不会让用户看到重复的内容。
  5. 缓存宕机、缓存击穿场景,一般判断用户是否在缓存中,如果在则直接返回结果,不在则查询db,如果来一波冷数据,会导致缓存大量击穿,造成雪崩效应,这时候可以用布隆过滤器当缓存的索引,只有在布隆过滤器中,才去查询缓存,如果没查询到,则穿透到db。如果不在布隆器中,则直接返回。
  6. WEB拦截器,如果相同请求则拦截,防止重复被攻击。用户第一次请求,将请求参数放入布隆过滤器中,当第二次请求时,先判断请求参数是否被布隆过滤器命中。可以提高缓存命中率。Squid 网页代理缓存服务器在 cache digests 中就使用了布隆过滤器。Google Chrome浏览器使用了布隆过滤器加速安全浏览服务
  7. Venti 文档存储系统也采用布隆过滤器来检测先前存储的数据
  8. SPIN 模型检测器也使用布隆过滤器在大规模验证问题时跟踪可达状态空间。
    (应用场景参考链接:https://www.zhihu.com/question/38211640/answer/1229656645)

5. 布隆过滤器的优缺点

  1. 误算率。随着存入的元素数量增加,误算率随之增加。这是因为本身哈希算法会有碰撞,同时bit数组中的index随着数据增加,碰撞几率增加。
  • 常见的补救办法是建立一个小的白名单,存储那些可能被误判的元素。但是如果元素数量太少,则使用散列表足矣。
  • 使用单条数据多哈希值映射,也就是一个key,生成多个hashcode值。如同一个key,做三次hashcode, 三次hashcode值都相同导致的碰撞会大大降低。但占用的bit数组空间会倍增,占用空间会增加,为了降低碰撞几率,创建的bit数组就需要扩大。
  1. 不能从布隆过滤器中删除元素,虽然很少有场景需要进行数据删除,但确实存在。
  • 一种解决方式就是进行bit数组重建,但这对于超大的bit数组来说,成本较高(需要针对key进行hashcode计算)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值