Bloom Filter 布隆过滤器
1)
最近在用python写一些爬虫程序,当需要通过html的url爬取所有层级链接的页面时.经常会遇到相同的url,这会导致很多页面重复爬取。因此需要设计算法,过滤掉已经下载过的url,大概的实现方法有:
- 将下载过的url保存到数据库,作为主键或类似的方法,保证其不重复
- 使用HashSet集合保证唯一性,可大大提高查询效率
- 对URL做摘要处理(例如MD5,SHA-1等算法),然后存储其映射
- 建立一个BitSet集合,将url通过hash函数映射到某一位
方法1在大量数据的情况下,查询速度堪忧.方法2 方法3又会占用大量内存.相比之下方法4,通过hash函数,只存储一个位标记,查询速度和存储上的问题都得到了解决,唯一的问题就是当大数据量时,较大概率会出现映射的冲突,导致查询错误。
2)
布隆过滤器,采用了方法4的思想,但是其采用了多个不同的hash函数来解决冲突问题。
由此可见,布隆过滤器,就是由一个位数组,以及n个hash函数构成的。当位url经过hash函数运算,对应位的值为0时,说明该url未被使用,反之,则表示已经使用过。当然,由于一定概率的冲突,会导致未使用过的url也映射到1位。但是对于使用过的url,绝对不会被映射到0位。因此,布隆过滤器适用于有一定容错的查重场景。
在爬取网页的情景中,其会导致少量的网页不会被爬取,但不会爬取已爬取过的网页。此缺点完全可以被接受。
3)
其java实现:
import java.util.BitSet;
public class BloomFilter {
/* BitSet初始分配2^24个bit */
private static final int DEFAULT_SIZE =1<<25;
/* 不同哈希函数的种子,一般应取质数 */
private static final int[] seeds =new int[] { 5, 7, 11, 13, 31, 37, 61 };
private BitSet bits =new BitSet(DEFAULT_SIZE);
/* 哈希函数对象 */
private SimpleHash[] func =new SimpleHash[seeds.length];
public BloomFilter() {
for (int i =0; i < seeds.length; i++){
func[i] =new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
// 将字符串标记到bits中
public void add(String value) {
for (SimpleHash f : func) {
bits.set(f.hash(value), true);
}
}
//判断字符串是否已经被bits标记
public boolean contains(String value) {
if (value ==null) {
returnfalse;
}
boolean ret =true;
for (SimpleHash f : func) {
ret = ret && bits.get(f.hash(value));
}
return ret;
}
/* 哈希函数类 */
public static class SimpleHash {
private int cap;
private int seed;
public SimpleHash(int cap, int seed) {
this.cap = cap;
this.seed = seed;
}
//hash函数,采用简单的加权和hash
public int hash(String value) {
int result =0;
int len = value.length();
for (int i =0; i < len; i++) {
result = seed * result + value.charAt(i);
}
return (cap -1) & result;
}
}
}
其python实现:
#!/usr/bin/python
#coding:utf-8
import cmath
from BitVector import BitVector
class BloomFilter(object):
def __init__(self, errorRate, elementNum):
#计算所需集合长度,公式见文末url
self.bitNum = -1 * elementNum * cmath.log(errorRate) / (cmath.log(2.0) * cmath.log(2.0))
#按四字节对齐
self.bitNum = self.align4Byte(self.bitNum.real)
#分配内存,生成类
self.bitArray = BitVector(size=self.bitNum)
#计算hash函数个数
self.hashNum = cmath.log(2) * self.bitNum / elementNum
self.hashNum = self.hashNum.real
#取整
self.hashNum = int(self.hashNum) + 1
#产生hash函数种子
self.hashSeeds = self.generateHashSeeds(self.hashNum)
def insertElement(self, element):
for seed in self.hashSeeds:
hashVal = self.hashElement(element, seed)
#取绝对值
hashVal = abs(hashVal)
#取模,防越界
hashVal = hashVal % self.bitNum
#设置相应的比特位
self.bitArray[hashVal] = 1
def isElementExist(self, element):
for seed in self.hashSeeds:
hashVal = self.hashElement(element, seed)
#取绝对值
hashVal = abs(hashVal)
#取模,防越界
hashVal = hashVal % self.bitNum
#判断相应的比特位
if self.bitArray[hashVal] == 0:
return False
return True
def align4Byte(self, bitNum):
num = int(bitNum / 32)
num = 32 * (num + 1)
return num
def generateHashSeeds(self, hashNum):
count = 0
#连续两个种子的最小差值
gap = 50
#初始化hash种子为0
hashSeeds = []
for index in xrange(hashNum):
hashSeeds.append(0)
for index in xrange(10, 10000):
maxNum = int(cmath.sqrt(1.0 * index).real)
flag = 1
for num in xrange(2, maxNum):
if index % num == 0:
flag = 0
break
if flag == 1:
if count > 0 and (index - hashSeeds[count -1]) < gap:
continue
hashSeeds[count] = index
count = count + 1
if count == hashNum:
break
return hashSeeds
def hashElement(self, element, seed):
hashVal = 1
for ch in str(element):
chval = ord(ch)
hashVal = hashVal * seed + chval
return hashVal
c语言实现:http://www.cnblogs.com/haippy/archive/2012/07/14/2590669.html