业务开发过程中,需要对某个对象进行标记或记录对象某个状态时,只要插入和读取两个操作,常用散列表、红黑树等数据结构记录。当记录数据量非常庞大时,消耗的内存会非常大,存储效率和查询效率也会非常低。如使用散列表记录1亿用户今日是否访问过,使用int存储用户id,需要381MB内存空间(4bytes100000000/1024/1024)。
每一个状态值使用4个字节存储会浪费很多内存,用户是否访问过,最少使用一个bit就可以表示。使用一个bit代表一个用户,1亿用户最小需要1亿bit,合记为11MB,大大减少了内存占用。
位图
位图可以在不占用很多内存的前提下,解决海量数据的存在性问题。
位图法的原理主要就是利用int类型数据,一个int类型数据是4个字节,一个字节8位,然后一个int数据利用自身字节位就可以表示0-31的数是否存在,bit位表示数值,0,1值表示这个数值是否存在。
如果要查找总数为 N 的数据存在性,只需要申请一个int 数组长度为int tmp[N/32+1]即可完成这些数据的存储。其中每个元素可标识32个数据的状态。
查找某一位的位置与状态
- 判断数字放在哪一个 tmp 数组中: 将数字直接除以 32 取整数部分 (x/32) ,例如:整数 8 除以 32 取整等于 0 ,那么 8 就在 tmp[0] 上;
- 确定数字放在32个位中的哪个位: 将数字mod32取模 (x%32) ,得到余数,即数字在一个元素中第几位。
- 查询此位状态: 将目标位右移至所在元素最右侧(即右移所在元素 x%32 位),与
1进行与运算,即可得到此位状态。更改状态可以通过类似操作实现。
对于多次出现的数据处理方法
然后我们怎么统计只出现一次的数呢?每一个数出现的情况我们可以分为三种:0次、1次、大于1次。也就是说我们需要用2个bit位才能表示每个数的出现情况。此时则三种情况分别对应的bit位表示是:00、01、11
我们顺序扫描这10亿的数,在对应的双bit位上标记该数出现的次数。最后取出所有双bit位为01的int型数就可以了。
优点:
- 节省内存空间
- 插入和查询时间复杂度都为
缺点:
- 存储数据不能重复
- 只能处理正整数的数据
- 对数据要求比较高,需要数据密度高时才能体现出高的空间效率
布隆过滤器(Bloom Filter)
布隆过滤器解决了位图在存储数据密度低时低效的问题。
布隆过滤器就是通过将元素进行多个Hash算法计算,都存入位图中,查询时使用同样的Hash算法计算,对应当所有值都为true时,表示存在。这样就可以极大的提升位图的存储效率。
💡 针对黑名单机制,布隆过滤器可以发挥很好的作用。
布隆过滤器也有致命的缺陷,即存在误判率,也称为假阳性率。当数据量不断增大,位图中非true位置越来越少,很可能会出现未插入的数据,查询结果为true。所以布隆过滤器的特点就是 False is always false. True is maybe true。因为误判率的存在,布隆过滤器多用于黑名单校验。
关于布隆过滤器准确率的计算
整个过程一共有两个未知数,使用 个哈希函数,以及取模 所构造的哈希表的范围。
设 为样本量, 为准确率。
💡 注意: 向上取整
优点:
- 提高了位图的存储效率
- 与散列表相比没有链表遍历,主要耗时为Hash计算,理论上时间效率更高
缺点:
- 存在误判率
- 无法删除数据
应用
- 黑名单校验
- 位图排序(桶排序)
- 快速去重
- ConsistencyCheck
- 爬虫URL校验
- 解决缓存穿透问题
位图高效的空间利用率只有在数据密集的情况下才能体现出来。 如:用户每日首次进入时弹窗广告,用户ID从10亿至20亿,用Integer存储。假设每日访问用户为n,存储共消耗32n个bit;用位图存储需要10亿个bit。所以当每日访问量在3125万人时,位图的存储效率才会超过列表存储。