BloomFilter - 布隆过滤器

BloomFiler又叫布隆过滤器,是一种思路,一种数据结构,特色是高效地插入和查询,能够用来告诉你 “某样东西必定不存在或者可能存在”。相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,可是缺点是其返回的结果是几率性的,而不是确切的。redis

 

使用场景数组

须要判断某个值是否存在,但存储量较大,存储空间浪费是个问题;且常规的比较查询效率较低数据结构

 

缺点函数

随着数据的增长,误判率随之增长,只能判断数据是否必定不存在,而没法判断数据是否必定存在;没法作到删除数据性能

 

实现原理搜索引擎

好比你有1个Url,你彻底能够建立一长度是100bit的数组,而后对url分别用5个不一样的hash函数进行hash,获得5个hash后的值,这5个值尽量的保证均匀分布在100个bit的范围内。而后把5个hash值对应的bit位都置为1,判断一个url是否已经存在时,一次看5个bit位是否为1就能够了,若是有任何一个不为1,那么说明这个url不存在。这里须要注意的是,若是对应的bit位值都为1,那么也不能确定这个url必定存在。url

 

BloomFilter的核心思想有两点spa

  1. 多个hash,增大随机性,减小hash碰撞的几率
  2. 扩大数组范围,使hash值均匀分布,进一步减小hash碰撞的几率。

尽管BloomFilter已经尽量的减少hash碰撞的几率了,可是,并不能完全消除,所以正如上面提到的:
若是对应的bit位值都为1,那么也不能确定这个url必定存在
也就是说,BloomFilter实际上是存在必定的误判的,这个误判的几率显然和数组的大小以及hash函数的个数以及每一个hash函数自己的好坏有关。设计

 

redis实现布隆过滤器code

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

用 redis来实现布隆过滤器,咱们要使用的数据结构是bitmap,你可能会有疑问,redis支持五种数据结构:String,List,Hash,Set,ZSet,没有bitmap呀。没错,实际上bitmap的本质仍是String。

要用redis来实现布隆过滤器,咱们须要本身设计映射函数,本身度量二进制向量的长度,这对我来讲,无疑是一个不可能完成的任务,只能借助搜索引擎,下面直接放出代码:

public class RedisMain {
    static final int expectedInsertions = 100;//要插入多少数据
    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("192.168.0.109", 6379);
        for (int i = 0; i < 100; i++) {
            long[] indexs = getIndexs(String.valueOf(i));
            for (long index : indexs) {
                jedis.setbit("codebear:bloom", index, true);
            }
        }
        for (int i = 0; i < 100; i++) {
            long[] indexs = getIndexs(String.valueOf(i));
            for (long index : indexs) {
                Boolean isContain = jedis.getbit("codebear:bloom", index);
                if (!isContain) {
                    System.out.println(i + "确定没有重复");
                }
            }
            System.out.println(i + "可能重复");
        }
    }

    /**
     * 根据key获取bitmap下标
     */
    private static long[] getIndexs(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) {
        Charset charset = Charset.forName("UTF-8");
        return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
    }

    //计算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)));
    }
}

 

如何删除

目前咱们知道布隆过滤器能够支持 add 和 isExist 操做,那么 delete 操做能够么,答案是不能够,例如一个 bit 位 被两个值共同覆盖的话,一旦你删除其中一个值而将其bit 位 置 0,那么下次判断另外一个值是否存在的话,会直接返回 false,而实际上你并无删除另外一个值。

如何解决这个问题,答案是计数删除。可是计数删除须要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增长一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。

该实现叫作 counting BloomFiler

 

 

若是但愿百分百准确,BloomFilter只适合作一层数据过滤(过滤掉必定不存在的),并不能彻底解决你的问题,还须要其余的解决方案。