布隆过滤器——初始状态
假设Bloom Filter使用一个m比特的数组来保存信息,初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0。
布隆过滤器——添加元素
将要添加的元素给k个哈希函数(Hash Function),也称之为散列函数, 得到对应于位数组上的k个位置,将这k个位置设为1。
现有S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。
增加任意一个元素x时候,我们使用k个哈希函数得到k个哈希值,然后将数组中对应的比特位设置为1。
布隆过滤器——查询元素
将要查询的元素给k个哈希函数,得到对应于位数组上的k个位置如果k个位置有一个为0,则肯定不在集合中;如果k个位置全部为1,则可能在集合中。
False Positive的比率为 f ,f 满足下列公式:
在给定m和n时,能够使f最小化的k值为:
此时给出的f为:
Hadoop中的 Bloom Filter
1. 默认的Bloom Filter 转自:Hadoop Bloom Filter 使用
BloomFilter filter =new BloomFilter(vectorSize, Hash, Hash.MURMUR_HASH);
Key key =new Key("hadoop".getBytes());
filter.add(key);
Key hb = new Key("hbase".getBytes());
boolean has =filter.membershipTest(key);
System.out.println(has);
System.out.println(filter.membershipTest(hb));
2. CountingBloomFilter 可以增加删除key
CountingBloomFilter filter =new CountingBloomFilter(vectorSize, Hash, Hash.MURMUR_HASH);
3. DynamicBloomFilter 过滤器长度可以扩容
DynamicBloomFilter filter =new DynamicBloomFilter(vectorSize, Hash, Hash.MURMUR_HASH, 1);
代码:
给定一个用户评论的列表,过滤掉大部分没有包含特定关键词的评论
public class TrainingBloomfilter {
public static int getOptimalBloomFilterSize(int numRecords, float falsePosRate) {
int size = (int) (-numRecords * (float) Math.log(falsePosRate) / Math
.pow(Math.log(2), 2));
return size;
}
public static int getOptimalK(float numMembers, float vectorSize) {
return (int) Math.round(vectorSize / numMembers * Math.log(2));
}
。。。。。
public class BloomFilterDriver {
public static void main(String[] args) throws Exception {
Path inputFile = new Path(args[0]);
int numMembers = Integer.parseInt(args[1]);
float falsePosRate = Float.parseFloat(args[2]);
Path bfFile = new Path(args[3]);
int vectorSize = getOptimalBloomFilterSize(numMembers, falsePosRate);
int nbHash = getOptimalK(numMembers, vectorSize);
BloomFilter filter = new BloomFilter(vectorSize, nbHash, Hash.MURMUR_HASH);
String line = null;
int numElements = 0;
FileSystem fs = FileSystem.get(new Configuration());
for (FileStatus status : fs.listStatus(inputFile)) {
BufferedReader rdr = new BufferedReader(new InputStreamReader(new GZIPInputStream(fs.open(status.getPath()))));
while ((line = rdr.readLine()) != null) {
filter.add(new Key(line.getBytes()));
++numElements;
}
rdr.close();
}
FSDataOutputStream strm = fs.create(bfFile);
filter.write(strm);
strm.flush();
strm.close();
System.exit(0);
}
}
。。。。
public static class BloomFilteringMapper extends Mapper<Object, Text, Text, NullWritable> {
private BloomFilter filter = new BloomFilter();
protected void setup(Context context) throws IOException, InterruptedException {
URI[] files = DistributedCache.getCacheFiles(context.getConfiguration());
System.out.println("Reading Bloom filter from: " + files[0].getPath());
DataInputStream strm = new DataInputStream(new FileInputStream(files[0].getPath()));
filter.readFields(strm);
strm.close();
}
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
Map<String, String> parsed = transformXmlToMap(value.toString());
String comment = parsed.get("Text");
StringTokenizer tokenizer = new StringTokenizer(comment);
while (tokenizer.hasMoreTokens()) {
String word = tokenizer.nextToken();
if (filter.membershipTest(new Key(word.getBytes()))) {
context.write(value, NullWritable.get());
break;
}
}
}
}
应用:
~ 字处理软件中,需要检查一个英语单词是否拼写正确
~ 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上
~ 在网络爬虫里,一个网址是否被访问过
~ yahoo, gmail等邮箱垃圾邮件过滤功能
垃圾邮件过滤。(用哈希表实现的具体办法是将每一个 email地址对应成一个八字节的信息指纹,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email地址需要占用十六个字节。一亿个地址大约要 1.6GB,即十六亿字节的内存)。因此存贮几十亿个邮件地址可能需要上百 GB的内存。而Bloom Filter只需要哈希表 1/8到 1/4 的大小就能解决同样的问题
方法:
~ 数组
~ 链表
~ 树、平衡二叉树、Trie
~ Map (红黑树)
~ 哈希表