位图与布隆过滤器学习笔记

467 阅读4分钟

位图

  业务开发过程中,需要对某个对象进行标记或记录对象某个状态时,只要插入和读取两个操作,常用散列表、红黑树等数据结构记录。

  当记录数据量非常庞大时,消耗的内存会非常大,存储效率和查询效率也会非常低。如使用散列表记录1亿用户今日是否访问过,使用int存储用户id,需要381MB内存空间(4bytes*100000000/1024/1024),加上链表的指针,消耗内存很可能会大于500M。

  没个状态值使用4个字节来存储浪费了很多内存,用户是否访问过,最少使用一个bit就可以表示。使用一个bit代表一个用户,1亿用户最小需要1亿bit,合记为11MB,大大减少了内存占用。

  虽然散列表与位图的插入、读取时间复杂度为O(1),但是位图不需要进行扩容、Hash计算,并且使用位运算,连续内存利于CPU缓存等特性,理论上时间效率也大于散列表。

  位图也有致命的缺点,当数据密度非常小时,位图的空间效率反而会低于散列表。如上例子中1亿用户的id分布在1到100亿之间,使用的空间1.16GB。

优点:
  1. 节省内存空间
  2. 插入和查询时间复杂度都为O(1)
缺点:
  1. 存储数据不能重复

  2. 只能处理正整数的数据

  3. 对数据要求比较高,需要数据密度高时才能体现出高的空间效率

布隆过滤器(Bloom Filter)

  布隆过滤器解决了位图在存储数据密度低时低效的问题。

  如上例中,1亿用户分布在1-100亿之间,我们使用10亿个bit存储这1亿个用户。通过一个Hash算法计算将用户分布在1-10亿之间,很大概率会出现Hash冲突的问题。但如果使用多个不同的Hash算法计算,不同用户最终的计算结果都不同的概率几乎可以忽略不计。

  布隆过滤器就是通过将元素进行多个Hash算法计算,都存入位图中,查询时使用同样的Hash算法计算,对应当所有值都为true时,表示存在。这样就可以极大的提升位图的存储效率。

img

  布隆过滤器也有致命的缺陷,即存在误判率,也称为假阳性率。当数据量不断增大,位图中非true位置越来越少,很可能会出现未插入的数据,查询结果为true。所以布隆过滤器的特点就是 False is always false. True is maybe true。因为误判率的存在,布隆过滤器多用于黑名单校验。

  可以通过Hash函数个数与预期插入数据个数,预测误判率,详见Guava实现的BloomFilter。如果插入数据过多,超出了预期的误判率。可以选择新建一个位图,将新数据插入到新的位图,但是在查询时可能需要多查询一个位图。

优点:
  1. 提高了位图的存储效率
  2. 与散列表相比没有链表遍历,主要耗时为Hash计算,理论上时间效率更高
缺点:
  1. 存在误判率
  2. 无法删除数据

具体实现

  1. JAVA BitSet
  2. Guava BloomFilter
  3. Redis BitMap

应用

  1. 黑名单校验
  2. 位图排序(桶排序)
  3. 快速去重
  4. ConsistencyCheck
  5. 爬虫URL校验
  6. 解决缓存穿透问题

注意事项

  1. 位图高效的空间利用率只有在数据密集的情况下才能体现出来。最近遇到的场景:用户每日首次进入时弹窗广告,用户ID从10亿至20亿,用Integer存储。假设每日访问用户为n,存储共消耗32n个bit;用位图存储需要10亿个bit。所以当每日访问量在3125万人时,位图的存储效率才会超过列表存储。

  2. 位图可以做到精准校验,布隆过滤器存在误判。