位图与布隆过滤器
位运算
了解位图, 首先得了解二进制
和位运算
大家可以通过下面的链接, 了解一下;
位图
什么是位图
位图: 在我看来就是通过使用一个字符或者数字, 来代表多个 1或0 的状态;
从而节省空间;
举个例子:
用户1和用户3都是程序员. 用户2,4不是
就可以使用二进制 (1表示是, 0表示不是) 0101
来表示这个情况
大家可以参考代码:
public class BitMap { // Java中char类型占16bit,也即是2个字节
private char[] bytes;
private int nbits;
public BitMap(int nbits) {
this.nbits = nbits;
this.bytes = new char[nbits/16+1];
}
public void set(int k) {
if (k > nbits) return;
int byteIndex = k / 16;
int bitIndex = k % 16;
// 1 << bitIndex 把 1 向左移动 bitIndex
// |= 表示 或等于
// 这句话可以让 该位置的bit 设置为 1
bytes[byteIndex] |= (1 << bitIndex);
}
public boolean get(int k) {
if (k > nbits) return false;
int byteIndex = k / 16;
int bitIndex = k % 16;
return (bytes[byteIndex] & (1 << bitIndex)) != 0;
}
}
为什么要使用位图
答: 因为节省空间
在计算机中, 数据都是以二进制的方式存储的;
在编程语言中 (例如Java) 变量所占的空间是相对固定的;
例如, bool 就是占1个字节, long 占8个字节;
假设现在有一个需求: 我们需要对用户加上标签 geek
;
我们有1000w用户;
方案1: 我们把用户的id, 放到一个hashmap中, 以用户的id为key,
如果用户是 geek
那么我们就把这个id的值设置为 true
这个方法是最容易想到的, 也是最好理解的;
但是, 不好的就是比较占空间;
一个int占4个字节, 1000w用户就是40M;
这么一看40M, 占用的空间不大;
但是, 这只是一个标签, 如果我们有几十个, 上百个, 内存肯定就不够用;
方案2: 我们使用1000w个bit, 来表示这1000w用户是否是geek;
例如 id为 100000001 的用户是geek, 那么第100000001位
的数据就是1;
这样空间上, 1000w用户会占用大概 12M左右的空间;
位图的好处与缺点
好处:
- 在上一节已经说明了, 就是节省空间;
- 可以借助位运算, 能够很快的对比出有多个标签的同一个人, 或者有标签1或标签2的人
这一点, 简单说一下;
假如有标签 geek 的用户bitmap是 1001 0110
有标签 cool 的用户的 bitmap 是 0101 0100
如果我需要找到一个用户他同时是 geek 和 cool
那么, 我只需要把两个二进制数 进行 与运算 就可以得到;
如果, 需要找到用户是geek 或者 cool, 那么需要做 或运算 即可;
但是, 如果是 找到不是 geek的用户
, 就不能
使用 非运算
了!
因为: 如果直接取反, 数据有可能是对不上的;
我们的用户是1000w, 可能中间有的数据被删除了, id是不连贯的;
所以, 不能够直接使用取反的数据;
坏处:
坏处
- 会浪费空间;
是不是感觉很不太对, 明明很节省空间的, 怎么又会浪费空间了呢?
因为: 在数据的范围特别大, 但是数据量很小. 那么这个bitmap 就会非常的稀疏;
例如: 我们统计出现过的数字
数据量是100 , 数据范围是 整数 从1到10亿的数据
如果使用bitmap, 就需要10亿个字节存储;
如果使用hashmap 就只需要 800个字节;
我们可以使用 布隆过滤器
来优化位图;
位图适用于哪种业务场景
位图适用的场景有
- 用户标签
- URL查重
- 数据排序
- 等等
它们都有一个特点, 就是数据的范围比较大, 数据量也比较大;
同时, 它们只需要一个 1/0 的状态即可;
redis
redis 中提供了很方便的位图运算
使用起来很方便; 真香警告!
redis 是使用 string 来存储位图
# 设置 第 111 位 为 1
setbit bit_map 111 1
getbit bit_map 111
# 会得到一个字符串
get bit_map
布隆过滤器
刚刚说过, 当数据范围很大时, 位图所占的bit位也会很大;
例如: 如果存储数据范围 从0 到 10亿的数据;
那么, 会需要10亿个bit位;
那么, 有没有什么办法能让位图所占的空间变小, 同时却能存储更多的信息呢?
什么是布隆过滤器
布隆过滤器 (Bloom Filter) 是基于位图的,是对位图的一种改进。
还是举例 10亿个数据;
布隆过滤器的做法是,我们使用一个 1 亿个二进制大小的位图,
然后通过哈希函数,对数字进行处理,让它落在这 1 到 1 亿范围内.
举例说明:
一个数字通过 多个hash 函数确定多个bit的位置;
只有这几个位置都是 1
, 才认定 这个点为 1; 否则就是 0;
但是, 布隆过滤器的误判有一个特点,那就是,它只会对存在的情况有误判
。
如果某个数字经过布隆过滤器判断不存在,那说明这个数字真的不存在,不会发生误判;
如果某个数字经过布隆过滤器判断存在,这个时候才会有可能误判,有可能并不存在。
不过,只要我们调整哈希函数的个数、位图大小跟要存储数字的个数之间的比例,那就可以将这种误判的概率降到非常低。
布隆过滤器解决了位图的什么问题
解决了位图在数据范围较大时, 所占空间较大的问题
布隆过滤器的好处与缺点
好处
- 节省空间
缺点
- 判断为1时, 存在误判的情况;
布隆过滤器适用于哪种业务场景
布隆过滤器非常适合这种不需要 100% 准确的、
允许存在小概率误判的大规模判重场景。除了爬虫网页去重这个例子,
还有比如统计一个大型网站的每天的 UV 数,也就是每天有多少用户访问了网站,我们就可以使用布隆过滤器,对重复访问的用户进行去重。