Bit-Map实现海量数据映射的简单算法
最近一直在搞搜索引擎,准确的说是丢了一个月又开始鼓捣搜索引擎,在做避免网页重复搜索时遇到了困难;打算用hash来映射url字符串从而避免重复搜索(神马是hash我就不重复了,自己补数据结构去),但是考虑到网页url的海量性,做一个demo的搜索引擎出来也不要太寒酸,所以设计的网页索引量预计在数千万到数亿左右,如果用朴素的hash的话,6G的内存(本机上跑)的话,全部用上映射128位hash只可以做到映射46875000个数(虽然128位理论上能表示3.4028236692094*10的38次方),32位hash则为187500000个数(因为原则上32位hash可以表示4294967296个数,但由于内存限制).可以看出,hash映射数据的瓶颈主要是实际的硬件内存限制(理论上的防碰撞下次再讲).
在网上鼓捣了一遍以及在各种参考资料参考书里面鼓捣了下之后找到了一个名为Bit-Map的算法,注意,这可不是位图的那个bitmap.它的思想是用一个位表示一个数,而非用8位,16位,32位或者64位甚至128位来表示一个数,把内存在位级别上去当成线性表使用,更准确地说,把内存当做一个连续的巨大的映射表,想像一下,这样的话,一个1Gbyte的内存空间可以存储映射80亿左右的整数范围.
话句话说,用128位整数存储8位电话号码需要800Mbytes+的空间,如果换做用Bit-Map方法来实现的话,这个空间消耗会一下子骤降到12Mbytes左右,虽然只是线性级别的降低,但是结果依然令人惊喜.
然后呢?我们该如何实现呢?
假设我们申请了一段连续的int类型的内存空间intmap[max_len+1],然后把它的位表示画出来:
用Bit-Map表示的话,第一个数组元素map[0]就用来映射0-31这32个数字(因为有这里有32bits),然后依次类推,每一个数组元素表示32个数字,当然,这32个数字是连续的.我们如上这样约定.
到现在,我们清楚了根本的算法思想,完了就是要解决一个一个实现的问题,第一个问题便是,如何把整型数映射到位中去?(这貌似是唯一一个问题).
对于以上这种规定,以下定理是成立的,那就是任意一个能够被映射的整数只会被映射到且被唯一映射到一个数组元素的某一个位上.
那么,假设我们要把一个整数N映射到Bit-Map中去,首先要确定把这个NMapping到哪一个数组元素中去,即确定映射元素的index.
我们是这样做的,记得我们在确定用int类型的数组作为map的元素吧,这样我们就知道了一个元素能够表示的数字个数(这里是32).于是N/32就可以知道我们需要映射的key了.所以余下来的那个N%32就是要映射到的位数.对于位的操作,我们直接用位运算符就是,这个到时再说.
然后,我们思考一下,我们能否用位运算完全代替这个算法里的所有算术运算(这是以前做竞赛的后遗症,一看到32,64,128就往位运算处想).其实除2的幂和对2的幂取模是可以用位实现的.
对于除法,我们用>>运算符实现,对于数N,N>>m的结果就是N/(2^m).
对于取模,我们用N=N&((1<<K)-1),这是对2^K取模,这个自己体会下.
第三个重要的操作就是在一个指定的32位二进制数的指定位置1了,可以直接用N|(2^m),就可以在N的第m位上置1了.这些所有的操作都可以用位操作实现.
现在可以来看一个例子了,就拿刚才那个映射8位电话号码的例子,例子的目的是将各个电话号码映射进map,从而使能够查询到某一个号码是否已经存在.
代码如下:
#include<iostream>
#include<math.h>
usingnamespace std;
//100000000/8=12500000
unsignedint map[3125001] ;
unsignedlong long int code ;
void init(){
for(inti = 0 ; i< 3125001 ; i++){
map[i]= map[i] & 0 ;
}
return;
}
int findKey(unsigned long long int code){
intidx = 0;
idx= code>>5;
return idx;
}
int findBit(unsigned long long int code){
intbit = 0;
bit= code&((1<<5)-1);
return bit;
}
void setInmap(unsigned long long int code){
intidx = findKey(code) ;
intbit = findBit(code) ;
map[idx]=map[idx]|((unsignedint)pow(2.0 , bit*1.0)) ;
return ;
}
int search(unsigned long long int code){
int idx = findKey(code);
int bit = findBit(code);
int temp = 0;
temp= map[idx];
temp= temp >> (bit) ;
if(temp%2==0)return 0;
else return 1;
}
int main(){
init();
cout<<"Input(I) Search(S) Exit(E) : "<<endl;
charc;
while(cin>>c){
if(c=='I'){
cout<<"InputCode : ";
cin>>code;
if(search(code)==0){
setInmap(code);
cout<<"InsertCode complete ! "<<endl;
}else{
cout<<"Codeexist . "<<endl;
}
cout<<"Input(I) Search(S) Exit(E) : "<<endl;
}
if(c=='S'){
cout<<"InputCode : ";
cin>>code;
if(search(code)==0){
cout<<"Codenot exit . "<<endl;
}else{
cout<<"Codeexit . "<<endl;
}
cout<<"Input(I) Search(S) Exit(E) : "<<endl;
}
if(c=='E'){
return 0;
}/*else{
cout<<"Unknowncommand ."<<endl;
cout<<"Input(I) Search(S) Exit(E) : "<<endl;
}*/
}
return0;
}
结果如下:
实现出来无压力,可以看到只开了3125001的数组,算一下只有十几兆而已,下次来分享一个更高端的用来判断映射是否重复的算法Bloomfilter.