布隆过滤器-BloomFilter

目录java

 

1、概述git

2、详解github

3、实现redis

4、适用业务场景算法


1、概述

简单讲布隆过滤器就是判断一个列表中是否存在某个元素。通常在JAVA判断是否存在,咱们能够Map,Set等容器。可是当数据量特别大的时候,用Map和Set会占用过多的内存。这个时候就会考虑用布隆过滤器了。数组

2、详解

要建立一个布隆过滤器首选须要在内存中声明一个Bit数组,假设数组的长度为L,初始值所有为0。缓存

       当put一个key到布隆过滤器的时候,会对key进行N次hash,而后对hash值 % L 取模,获得N个位置下标。而后将Bit数组中对应位置的值所有设置为1。其中的L和N取决于key的预估总数和错误率,由于bloomfilter不能保证100%的准确,这个后面会说。数据结构

       当判断一个key是否存在的时候也是对key进行N次hash取模,若是全部bit数组中全部位置的值都为1,则认为这个key有可能存在,注意这里说是有可能。测试

过程以下图:(这里假设L为10, N为3)this

假设:

"zhangsan":3次hash取模的结果为:0,2,4。

"lisi":3次hash取模的结果为:4,6,8。

"wangwu":3次hash取模的结果为:2,4,6。

若是已经存在"zhangsan"和"lisi"这俩个key,那么即便"wangwu"这个key实际不存在,可是算法返回的结果是存在,由于2,4,6这个三个位置已经被“zhangsan”和“lisi”占用了。

综上所述发现bloomfiter有一些特色:

  1. 若是算法返回不存在,那刻个key确定不存在。
  2. 若是算法返回存在,那只能说明有可能存在。
  3. bloomfiter中的key没法删除。由于bit位是复用的,删除会影响别的key。

那么怎么提高算法的准确度呢?

  1. 增长hash的次数(CPU和准确度的取舍)
  2. 增长bit数组的长度(内存和准确度的取舍)

3、实现

  1. 本身写java代码实现
    package com.ikuboo.bloomfilter;
    
    import java.util.BitSet;
    
    /**
     * 布隆过滤器
     */
    public class MyBloomFilter {
    
        private int length;
    
        /**
         * bitset
         */
        private BitSet bitSet;
    
        public MyBloomFilter(int length) {
            this.length = length;
            this.bitSet = new BitSet(length);
        }
    
        /**
         * 写入数据
         */
        public void put(String key) {
            int first = hashcode_1(key);
            int second = hashcode_2(key);
            int third = hashcode_3(key);
    
            bitSet.set(first % length);
            bitSet.set(second % length);
            bitSet.set(third % length);
        }
    
        /**
         * 判断数据是否存在
         *
         * @param key
         * @return true:存在,false:不存在
         */
        public boolean exist(String key) {
            int first = hashcode_1(key);
            int second = hashcode_2(key);
            int third = hashcode_3(key);
    
            boolean firstIndex = bitSet.get(first % length);
            if (!firstIndex) {
                return false;
            }
            boolean secondIndex = bitSet.get(second % length);
            if (!secondIndex) {
                return false;
            }
            boolean thirdIndex = bitSet.get(third % length);
            if (!thirdIndex) {
                return false;
            }
            return true;
        }
    
        /**
         * hash 算法1
         */
        private int hashcode_1(String key) {
            int hash = 0;
            int i;
            for (i = 0; i < key.length(); ++i) {
                hash = 33 * hash + key.charAt(i);
            }
            return Math.abs(hash);
        }
    
        /**
         * hash 算法2
         */
        private int hashcode_2(String data) {
            final int p = 16777619;
            int hash = (int) 2166136261L;
            for (int i = 0; i < data.length(); i++) {
                hash = (hash ^ data.charAt(i)) * p;
            }
            hash += hash << 13;
            hash ^= hash >> 7;
            hash += hash << 3;
            hash ^= hash >> 17;
            hash += hash << 5;
            return Math.abs(hash);
        }
    
        /**
         * hash 算法3
         */
        private int hashcode_3(String key) {
            int hash, i;
            for (hash = 0, i = 0; i < key.length(); ++i) {
                hash += key.charAt(i);
                hash += (hash << 10);
                hash ^= (hash >> 6);
            }
            hash += (hash << 3);
            hash ^= (hash >> 11);
            hash += (hash << 15);
            return Math.abs(hash);
        }
    
    }

    测试代码

    public class TestMyBloomFilter {
        public static void main(String[] args) {
            int capacity = 10000000;
            MyBloomFilter bloomFilters = new MyBloomFilter(capacity);
            bloomFilters.put("key1");
    
            System.out.println("key1是否存在:" + bloomFilters.exist("key1"));
            System.out.println("key2是否存在:" + bloomFilters.exist("key2"));
        }
    }

     

  2. guava类库实现
    import com.google.common.hash.BloomFilter;
    import com.google.common.hash.Funnels;
    
    import java.nio.charset.Charset;
    
    public class TestGuavaBloomFilter {
        public static void main(String[] args) {
            //预估的容量
            int capacity = 10000000;
            //指望的错误率
            double fpp = 0.01;
    
            BloomFilter<String> bloomFilters = BloomFilter.create(
                    Funnels.stringFunnel(Charset.forName("UTF-8")), capacity, fpp);
    
            bloomFilters.put("key1");
    
            System.out.println("key1是否存在:" + bloomFilters.mightContain("key1"));
            System.out.println("key2是否存在:" + bloomFilters.mightContain("key2"));
        }
    }

     

  3. 配合redis实现
    1. 能够本身写代码利用redis bitmap数据结构显示。能够参考guava里面的计算hash次数和hash的想相关代码,就guava里的BitArray替换为redis的bitmap便可。
    2. 能够用lua脚本实现。参考:https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
    3. 利用redis 4.0+提供的插件功能实现。参考:https://blog.csdn.net/u013030276/article/details/88350641

4、适用业务场景

  1. 防止缓存穿透
  2. 去重,幂等处理等流程