布隆过滤器

四、布隆过滤器

日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中,比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。
一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。
比如说,一个像 Yahoo、Hotmail 和 Gmail 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spammer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。
如果用哈希表,每存储一亿个 email 地址,就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB,即十六亿字节的内存)。因此存储几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的。

  1. 用哈希表存储用户记录,缺点:浪费空间。
  2. 用位图存储用户记录,缺点:位图一般只能处理整形,如果内容编号是字符串,就无法处理了。
  3. 将哈希与位图结合,即布隆过滤器。

0、布隆过滤器概念

布隆过滤器是由布隆(Burton Howard Bloom)在1970年提出的 一种紧凑型的、比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你**“某样东西一定不存在或者可能存在”**,它是用多个哈希函数,将一个数据映射到位图结构中。此种方式不仅可以提升查询效率,也可以节省大量的内存空间。
image.png
image.png

1、布隆过滤器查找

image.png
image.png

时间复杂度与哈希函数的个数成正比也就是O(K),K位哈希函数的个数。

3、布隆过滤器模拟实现

import java.util.BitSet;

class SimpleHash {

    public int cap;//当前容量
    public int seed;//随机

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

    //根据seed不同 创建不能的哈希函数
    int hash(String key) {
        int h;
        //(n - 1) & hash
        return (key == null) ? 0 : (seed * (cap-1)) & ((h = key.hashCode()) ^ (h >>> 16));
    }

}
public class MyBloomFilter {

    public static final int DEFAULT_SIZE = 1 << 20;
    //位图
    public BitSet bitSet;
    //记录存了多少个数据
    public int usedSize;

    public static final int[] seeds = {5,7,11,13,27,33};

    public SimpleHash[] simpleHashes;

    public MyBloomFilter() {
        bitSet = new BitSet(DEFAULT_SIZE);
        simpleHashes = new SimpleHash[seeds.length];
        for (int i = 0; i < simpleHashes.length; i++) {
            simpleHashes[i] = new SimpleHash(DEFAULT_SIZE,seeds[i]);
        }
    }

    /**
     * 添加元素 到布隆过滤器
     * @param val
     */
    public void add(String val) {
        //让X个哈希函数  分别处理当前的数据
        for (SimpleHash simpleHash : simpleHashes) {
            int index = simpleHash.hash(val);
            //把他们 都存储在位图当中即可
            bitSet.set(index);
        }
    }
    /**
     * 是否包含val ,这里会存在一定的误判的
     * @param val
     * @return
     */
    public boolean contains(String val) {
        //val  一定 也是通过这个几个哈希函数去 看对应的位置
        for (SimpleHash simpleHash : simpleHashes) {
            int index = simpleHash.hash(val);
            //只要有1个为 0     那么一定不存在
            boolean flg = bitSet.get(index);
            if(!flg) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        MyBloomFilter myBloomFilter = new MyBloomFilter();
        myBloomFilter.add("hello");
        myBloomFilter.add("hello2");
        myBloomFilter.add("bit");
        myBloomFilter.add("haha");

        System.out.println(myBloomFilter.contains("hello"));
        System.out.println(myBloomFilter.contains("hello3"));
        System.out.println(myBloomFilter.contains("he"));
    }
}
package org.example.bloomfilterdemo;

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

/**
 * @Author huis
 */
public class Test {
    private static int size = 1000000;//预计要插入多少数据

    private static double fpp = 0.001;//期望的误判率

    private static final BloomFilter<Integer> BLOOM_FILTER
            = BloomFilter.create(Funnels.integerFunnel(), size, fpp);

    public static void main(String[] args) {
        //插入数据
        for (int i = 0; i < 1000000; i++) {
            BLOOM_FILTER.put(i);
        }

        int count = 0;
        for (int i = 1000000; i < 2000000; i++) {
            if (BLOOM_FILTER.mightContain(i)) {
                count++;
            }
        }
        System.out.println("总共的误判数:" + count);
    }
}

<dependencies>
  <dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
  </dependency>
</dependencies>

4、 布隆过滤器删除

布隆过滤器不能直接支持删除工作,因为在删除一个元素时,可能会影响其他元素。比如:删除上图中"tencent"元素,如果直接将该元素所对应的二进制比特位置0,“baidu”元素也被删除了,因为这两个元素在多个哈希函数计算出的比特位上刚好有重叠。
**一种支持删除的方法:**将布隆过滤器中的每个比特位扩展成一个小的计数器,插入元素时给k个计数器(k个哈希函数计算出的哈希地址)加一,删除元素时,给k个计数器减一,通过多占用几倍存储空间的代价来增加删除操作。这种方法的缺陷:

  1. 无法确认元素是否真正在布隆过滤器中【会有误判】
  2. 存在计数回绕【回绕意思为:溢出】

5、 布隆过滤器优点

  1. 增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,一般比较小),与数据量大小无关
  2. 哈希函数相互之间没有关系,方便硬件并行运算
  3. 布隆过滤器不需要存储元素本身,在某些对保密要求比较严格的场合有很大优势
  4. 在能够承受一定的误判时,布隆过滤器比其他数据结构有这很大的空间优势
  5. 数据量很大时,布隆过滤器可以表示全集,其他数据结构不能
  6. 使用同一组散列函数的布隆过滤器可以进行交、并、差运算;

6、布隆过滤器缺陷

  1. 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再建立一个白名单,存储可能会误判的数据)
  2. 不能获取元素本身
  3. 一般情况下不能从布隆过滤器中删除元素
  4. 如果采用计数方式删除,可能会存在计数回绕问题

7、布隆过滤器使用场景

  1. google的guava包中有对Bloom Filter的实现
  2. 网页爬虫对URL的去重,避免爬去相同的URL地址。
  3. 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否是垃圾邮箱。
  4. 解决数据库缓存击穿,黑客攻击服务器时,会构建大量不存在于缓存中的key向服务器发起请求,在数据量足够大的时候,频繁的数据库查询会导致挂机。
  5. 秒杀系统,查看用户是否重复购买。

8、 海量数据类型面试题

0、哈希切割
  1. **给一个超过100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址? 与上题条件相同, 如何找到topK的IP? **

**答:**如果忽略大小,我们可以统计每个IP出现的次数。我们可以使用<K,V>结构来解决这个题。但是问题目前是100G的数据太大了,一次性是无法加载到内存当中的。

思路:尝试把当前这1个文件给拆分成若干个小文件。问题是如何拆分?如果按照大小将100G进行均分,会出现一个情况,一个文件当中最多的IP地址,不一定就是整体上最多的IP地址。所以,均分解决不了。
哈希切割:将相同的IP字符串存储到同一个文件(文件组或文件夹)当中去。
a、将IP字符串转化为整数
b、文件下标 = hash(IP),将IP存储到对应的小文件中去
c、读取每个文件的内容,统计每个IP出现的次数

1、位图应用
  1. 给定100亿个整数,设计算法找到只出现一次的整数?

**方法一:哈希切割:**将相同数字哈希切割到同一个文件中去,遍历每个文件,统计每个数字出现的次数,此时内存中就可以知道那些数字只出现了一次。

**方法二:两个位图: **若bitSet1[num]=1,bitSet1[num]=0,则说明num只出现了一次
image.png
**方法三:一个位图:**使用两个比特位从当小型计数器,统计数字出现的个数
image.png

  1. 给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件的交集?

方法一:哈希切割

对两个大文件分别进行哈希切割,切割若干小文件到两个文件夹中(此过程可以顺手对文件排序),然后遍历两个文件夹中的文件求出交集。
方法二:位图
将两个文件的数据添加到用两个位图中,为两个位图做按位与操作就可以求出交集

  1. 位图应用变形:1个文件有100亿个int,1G内存,设计算法找到出现次数不超过2次的所有整数。

答:哈希切割、两个位图

3、布隆过滤器
  1. 给两个文件,分别有100亿个query,我们只有1G内存,如何找到两个文件交集?分别给出精确算法和近似算法。

答:

方法一:哈希切割(精确计算)
方法二:布隆过滤器(近似计算)
image.png

  1. 如何扩展BloomFilter使得它支持删除元素的操作。

答:把位图的每个单位都设计成一个小型计数器。

扩展阅读:一致性哈希哈希与加密布隆过滤器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值