引子
首先通过一道题来理解什么是bitmap。
题目:我有40亿个整数,再给一个新的整数,我需要判断新的整数是否在40亿个整数中,你会怎么做?
分析:
假设一个int占4个字节(32位),40个亿个整数就是160亿个字节,大概相当于16GB,假设一台计算机只有2GB内存,则16GB一次加载不完,需要分8次加载,从磁盘加载数据是磁盘io操作,是非常慢的(比内存中的操作要慢100倍),每次加载这么大的数据,并且要8次,那么查找的时间可以达到分钟甚至小时级。
还有一个办法是把数据分散在8台计算机上,然后来一个新数据,8台计算机同时找,然后再汇总结果。这样每台计算机都可以一次性把数据读入内存中,查找就不用来回加载数据,省去了加载数据的开销,是个好方法。
但是否还有其他更好的方法呢?那就是bitmap。bitmap存值的思路:每一个int有32位,int整数的范围是-2147483648 ~ 2147483647。为简化理解,这里先假设每一个整数位均为正整数(如果存在负整数需分开处理),2147483647/32 = 67108863,即只需要67108863个int型整数就可以表示 [0,2147483647] 范围的数字,即需要67108863*4 = 268,435,452个字节的内存,相当于0.2GB,即使加上负整数部分也才需要0.4GB的内存,一台计算机完全足够。这里将开辟67108863int型数组,数组中的每一位代表依次代表 [0,2147483647]。而且而且判断新的整数也只需要O(1)的时间复杂度,性能非常高。
bitmap定义
位图是一个数组的每一个数据的每一个二进制位表示一个数据,0表示数据不存在,1表示数据存在。
例如存储136这个数:
- 确定136在整个数据的那个区间,136/32 = 4,即在第四个区间;
- 确定136在这个区间的第几位(bit),136%32 = 25,即在第四区间的第25位上;
- 将这个位置置为1,表示存在这个数。
由于bitmap的数据存储方式,具有升序排序的性质。
代码实现
#include <iostream> #include <vector> using namespace std; class Bitmap { public: Bitmap() { bitVec_.resize((INT_MAX >> 5) + 1); //多开辟一个空间,原因是数组只能表示区间[0,size) } void BitmapSet(int val) { int index = val >> 5; //相当于除以32,用移位操作可提高性能 int offset = val % 32; bitVec_[index] |= (1 << offset); int capacity = bitVec_.capacity(); } bool BitmapGet(int val) { int index = val >> 5; int offset = val % 32; return bitVec_[index] & (1 << offset); } private: vector<unsigned int> bitVec_; }; int main() { Bitmap bm; //这里只存[0,1000000]的数, for (int i = 0; i <= 1000000; ++i) { bm.BitmapSet(i); } bool exist1 = bm.BitmapGet(100); // 100是否存在,返回true bool exist2 = bm.BitmapGet(10000000); // 10000000是否存在,返回false system("pause"); return 0; }
上面实现只是针对开篇题目写的简单bitmap,下一篇文章会探讨Bloom Filter的原理,会对用到的bitmap进行优化修改。