Bootstrap

第二章 分布式数据库设计准则之布隆过滤器~判断单值是否存在

判断值存在 - 布隆过滤器Bloom Filter

  • 业务场景:大量数据判断单值是否存在且大量数据远远超出服务器的内存,无法使用HashMap,因为HashMap的时间复杂度虽然为O(1),但是其数据都是在内存中的。

1、布隆过滤器数据结构

​ 一种占比空间很小、空间效率很高的随机数据结构,有一个很长的二进制向量一组Hash映射函数组成。

  • 一个大型位数组(二进制数组)

位数组.png

  • 多个Hash映射函数:元素的hash值均匀映射到位数组

布隆过滤器.png

2、实现原理

(1)初始状态

​ 布隆过滤器需要的是一个位数组和K个映射函数,在初始状态时,对于长度为m的位数组array,他的所有位被置0

在这里插入图片描述

(2)数据插入

​ 对于有n个元素的集合S={S1,S2…Sn},通过k个映射函数{f1,f2…fk},将集合中的每个元素都映射为K个值{g1,g2…gk},然后再将数组array中相对应的array[g1],array[g2]…array[gk]置为1

在这里插入图片描述

  • 插入案例1:“baidu” 和三个不同的哈希函数分别生成了哈希值 1、4、7。

在这里插入图片描述

  • 插入案例2:“tencent” 和三个不同的哈希函数分别生成了哈希值 3、4、8。

在这里插入图片描述

(3)数据查找

在这里插入图片描述
​ 要查找某个元素item是否在S中,则通过映射函数{f1,f2,…fk}得到k个值{g1,g2…gk},然后再判断 array[g1],array[g2]…array[gk]是否都为1,若全为1,则item在S中,否则item不在S中。这个就是布隆 过滤器的实现原理。

在这里插入图片描述

S1的Hash查找结果:1,3,12  位数组中都有相关值,则判定存在
S4的Hash查找结果:2,9,12  为数组中只有一个存在,则判定不存在

1.3、布隆过滤器优缺点

(1)优点

①远远缩小存储空间的数据规模

  • 布隆过滤器不需要存储元素本身

  • 布隆过滤器可以表示全集,其他任何数据结构都不能。

②能快速判断元素存在不存在

  • Hash函数相互之间没有关系,方便由硬件并行执行
  • k和m相同,使用同一组Hash函数的两个布隆过滤器的交并差运算可以使用位操作进行。

(2)缺点

①有一定误判率

  • 本质上是由Hash冲突引起的,原本不存在于该集合的元素,布隆过滤器有可能会判断说它存在,但是如果布隆过滤器判断某一个元素不存在该集合,那么该元素一定不在该集合内。

②不支持删除操作

  • 删除元素首先确保元素的确在布隆过滤器里面,但是布隆过滤器无法保证

1.4、误判率估计

​​ 误判本质上是Hash冲突导致的,布隆过滤器解决哈希冲突的方法是多哈希法

(1)多哈希法

设计二种甚至多种哈希函数,可以避免冲突,但是冲突几率还是有的,函数设计的越好或越多都可以将几率降到最低(除非人品太差,否则几乎不可能冲突)。

(2)误判率计算

  • 业务场景

​ 位数组大小:m

​ 总共数据大小为:n

​ hash函数的个数为:k

①对某一特定bit位在一个元素,调用hash函数之后,其改成1的概率是
1 / m 1/m 1/m
②对某一特定bit位在一个元素由单个hash函数没有置为1的概率
1 − 1 / m 1-1/m 11/m
③对某一特定bit位在一个元素由k个hash函数没有置为1的概率
( 1 − 1 / m ) k (1-1/m)^k (11/m)k
④当n个数据插入进来,某一个bit位没有被置为1的概率
[ ( 1 − 1 / m ) k ] n [(1-1/m)^k]^n [(11/m)k]n
⑤当n个数据插入进来,某一个bit位被置为1的概率
1 − [ ( 1 − 1 / m ) k ] n 1-[(1-1/m)^k]^n 1[(11/m)k]n
⑥查询单个元素,则调用k个hash函数得到k个位置值,然后再去判断这k个位置值是不是1
计 算 出 来 的 k 个 位 置 值 都 是 1 的 概 率 = [ 1 − [ ( 1 − 1 / m ) k ] n ] k 计算出来的k个位置值都是1的概率 = [1-[(1-1/m)^k]^n]^k k1=[1[(11/m)k]n]k

  • 总而言之:当m增大或者n减小时,都会使误差率减小

  • 总结:

    • 位数组长度:长度会直接影响误报率,布隆过滤器越长其误报率越高
    • 哈希函数个数:个数越多则布隆过滤器bit位置变1的速度越快,且布隆过滤器的效率越低,个数太少,则误报率会变高

1.5、最优哈希个数

​ 对于给定的m和n时,k为何值时可以使误判率最低,设误判率的函数如下:在这里插入图片描述

​ 当m和n确定了以后,k为以下值时,误判率是最低的。
k ≈ 0.7 ∗ ( m / n ) k≈0.7*(m/n) k0.7(m/n)

  • 总结:如想保持某固定误判率不变,布隆过滤器的bit数m和被add的元素数n应该是线性同步增加的。

1.6、布隆过滤器设计实战

(1)业务场景

​ 有两个URL集合A,B,每个集合中大约有1亿个URL,每个URL占64字节,有1G的内存,如何找出两个集合中重复的URL

(2)解决思路

①直接利用Hash表会超出内存限制的范围

②如果不允许一定的错误率的话,则使用分治思想

​ 将A,B两个集合中的URL分别存到若干个文件中{f1,f2…fk}和{g1,g2…gk}中,然后取f1和g1的内容读入内存,将f1的内容存储到hash_map当中,然后再取g1中的url,若有相同的url,则写入到文件中,然后直到g1的内容读取完毕,再取g2…gk。然后再取f2的内容读入内存…,依次类推,知道找出所有的重复url。

在这里插入图片描述

③如果允许有一定错误率的话,则可以使用布隆过滤器

public class MyBloomFilter {
    // 位数组的大小
    private final static int DEFAULT_CAPACITY = 2 << 22;

    // 实现不同hash函数的参数数组
    private final static int[] SEEDS = {3, 13, 46, 76, 91, 138};

    // 定义位数组
    private final BitSet bits = new BitSet(DEFAULT_CAPACITY);

    // 存放哈希函数的类数组
    private final SimpleHash[] func = new SimpleHash[SEEDS.length];

    // 对哈希函数进行初始化
    public MyBloomFilter() {
        for (int i = 0; i < SEEDS.length; i++) {
            func[i] = new SimpleHash(DEFAULT_CAPACITY, SEEDS[i]);
        }
    }

    // 添加元素到位数组操作
    public void add(Object value) {
        for (int i = 0; i < SEEDS.length; i++) {
            bits.set(func[i].hash(value), true);
        }
    }

    // 判断是否存在操作
    public boolean contains(Object value) {
        boolean ret = true;
        for (SimpleHash f : func) {
            ret &= bits.get(f.hash(value));
        }
        return ret;
    }

    /*
        静态内部类:用于实现不同的哈希函数
     */
    private static class SimpleHash {
        private final int cap;
        private final int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        /**
         * 计算哈希值
         */
        public int hash(Object key) {
            int h;
            return (key == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = key.hashCode()) ^ (h >>> 16)));
        }
    }

}
;