对于广大程序员来说,“判断一个值是否存在”这么一个问题,可能新手会说遍历(数组),老手们会直接抛下一句“哈希表呗”然后不屑的离开。
但是经过我们源码的分析可知,HashMap的判断,摊还分析确实是O(1)搜索时间,但是实际上就找到对应节点的过程(可以看我前面的博客)还是需要一定的时间的。假设我们构造好了链表之后,某一个位置的链表长度为7(嘿嘿,如果是8就是红黑树了,需要3次搜索),那么我们每一次判断都需要一个7次搜索,次数过多必然效率有所下降。
那么,布隆过滤器是干什么的呢?
它可以给你一个对于这个值存在还是不存在的一个不准确回答:
可能存在。。。 / 一定不存在!
首先它是一个类似于位图一样的东西:(图片来源于知乎)
这个位代表一个byte,这个时候我们需要把一个key映射成几个hash值(你也可以选择只hash一次,但正确率肯定不高)。
比如单词“hello”三次hash(三个hash函数是不一样的)生成三个值为 (1,4,7),那么就变成:
然后单词“world”同理,生成的三个值是(3,4,8):
由于这里面hash只能使某一位“置位”,所以如果有个单词“Java”生成了(1,7,8),这里面我们可以看到确实这三个位置都处于置位状态,那么我们就可以说一定存在吗?答案是:不一定。
所以综上所述,如果生成的几个hash值都被置位那么可以说“可能存在”,否则我们就说“一定不存在”。
那其实,Bloom使用的正确姿势是什么呢?就是作为hash表(可能是HashMap也可能是ConcurrentHashMap,也可能是Redis之类的类NoSQL)的辅助结构,有了它能以一定的概率过滤不存在的搜索。
顺便贴出两个公式:
但其实,实际中的设计并不一定按照这个公式。
好了说了这么多,我写了一个Java版的简易过滤器,可以看看效果。
/**
* @author wang66
* 2019-8-12
* Bloom for String
*/
public class Bloom {
private int[] record=new int[1024];//每一个int代表32位
private int size=32*1024;//位总长度
private int hash1(String s){//第一个hash值
int h=0;
for(char c:s.toCharArray()){
h=h*37+(int)(c);
}
return Math.abs(h);
}
private int hash2(String s){//第二个hash值
int h=0;
for(char c:s.toCharArray()){
h=h*61+(int)c;
}
return Math.abs(h);
}
private int hash3(String s){//第三个hash值
int h=0;
char[] chars=s.toCharArray();
for(int i=chars.length-1;i>=0;i--){
h+=h*73+(int)chars[i];
}
return Math.abs(h);
}
public int hash4(String s){//第四个hash值
int h=0;
int j=1;
char[] chars=s.toCharArray();
for(char c:chars){
h=h*41+(int)(c)*j;
j*=-1;
}
return Math.abs(h);
}
public void put(String s){//把一个单词放进去
int h1=hash1(s)%size;
int h2=hash2(s)%size;
int h3=hash3(s)%size;
int h4=hash4(s)%size;
record[h1/32]|=(1<<(h1%32));
record[h2/32]|=(1<<(h2%32));
record[h3/32]|=(1<<(h3%32));
record[h4/32]|=(1<<(h4%32));
}
public boolean isMaybeExist(String s){//判断是否“可能存在”
int h1=hash1(s)%size;
int h2=hash2(s)%size;
int h3=hash3(s)%size;
int h4=hash4(s)%size;
return (record[h1/32]&(1<<(h1%32)))!=0&&
(record[h2/32]&(1<<(h2%32)))!=0&&
(record[h3/32]&(1<<(h3%32)))!=0&&
(record[h4/32]&(1<<(h4%32)))!=0;
}
public static void main(String[] args){
Bloom bloom=new Bloom();
bloom.put("Hello");
bloom.put("world");
bloom.put("friend");
bloom.put("family");
bloom.put("future");
System.out.println(bloom.isMaybeExist("world"));
System.out.println(bloom.isMaybeExist("poor"));
System.out.println(bloom.isMaybeExist("future"));
}
}
三个输出结果分别是
true
false
true
好了这个数据结构到此结束。