bloomfilter 原理及实现
2016-09-03 16:10
78 查看
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">参考文章:</span> http://blog.csdn.net/u013402746/article/details/28414901
原理:
使用一组hash函数将key映射为一组整数(如3、5、10),再将一个大的bit数组的相应位置置1
最终使用这个bit数组作为keys的集合
查询时:
对输入x进行同样的一组hash,假设结果为3、5、10,则查看bit数组的相应位置,若都为1,则说明x有很大概率在集合内
(注意,可能a->(1,3,7)与b->(2,5,10)的叠加导致3、5、10都为1,从而产生误判 False positive)
但若3个位置有1个不为1,则x肯定不在集合中
总结:
bloomfilter是一个集合,用于添加和快速检索,最大优势是省内存(相比于hash);并且有一定的误判率
实现:
1、python中实现bit数组,可以借助现有工具bitvector,也可以自己用array实现,我的实现如下:
只需实现几个简单功能即可,获取第i位的状态0-1,将第i位置1;使用unsigned char array实现
2、实现hash函数
给出不同的种子,将string映射为整数,并且用%操作来控制输出值小于cap,即小于bitset的长度
hash的实现可以参考这篇: http://blog.csdn.net/mycomputerxiaomei/article/details/7641221
3、bloom过滤器实现
根据误差和输入个数的计算公式,可以控制误差率
4、效果验证
首先定义一个随机字符串生成函数:
随后给出测试函数:
控制误差在千分之一,结果如下:
可以看到,使用9个hash函数来进行映射,存储1w个字符串,bit长度为1298425,大小为1298425.0/8/1024 = 158k
其误差率为0.0048,即10000个中有48个不在集合中,但被误判在集合内。
相比于python内建的set类型,我们的方法速度很慢,这个是由实现导致的。
hash的实现,每个字符串对应为一个8字节的信息指纹,大小为8*10000/1024*2 = 156k
注意,实际上我的bitset是按照容纳10**5个数来设计的,如果改为10**4,则容量bitset长度可以缩小一倍,同时误差率在2%左右
这样来看,用bloomfitler实现的set()容量仅为传统hashset的1/10左右
原理:
使用一组hash函数将key映射为一组整数(如3、5、10),再将一个大的bit数组的相应位置置1
最终使用这个bit数组作为keys的集合
查询时:
对输入x进行同样的一组hash,假设结果为3、5、10,则查看bit数组的相应位置,若都为1,则说明x有很大概率在集合内
(注意,可能a->(1,3,7)与b->(2,5,10)的叠加导致3、5、10都为1,从而产生误判 False positive)
但若3个位置有1个不为1,则x肯定不在集合中
总结:
bloomfilter是一个集合,用于添加和快速检索,最大优势是省内存(相比于hash);并且有一定的误判率
实现:
1、python中实现bit数组,可以借助现有工具bitvector,也可以自己用array实现,我的实现如下:
#%% 自定义bitset数组 import array class my_bitset(object): ''' 自定义bitset数组,共sz位 占用_array_sz个byte array 用于bloom过滤器使用 ''' def __init__(self, sz=1): self.sz = sz self._array_sz = sz/8 if sz%8==0 else sz/8+1 # 'B' unsigned char,以此模拟bitset self.array = array.array('B', [0]*self._array_sz) def __contains__(self, v): return True if self.__getitem__(v) else False ## 获取bieset的第i位是否为1(从左往右数) def __getitem__(self, i): i_arr,i_byte = i/8,i%8 k = 128>>i_byte return (self.array[i_arr] & k)!=0 ## 设置bieset的第i位为1 def __setitem__(self, i, value=1): i_arr,i_byte = i/8,i%8 k = 128>>i_byte # 如果该位为1,则不操作;为0则加上k if not self.__getitem__(i): self.array[i_arr] += k
只需实现几个简单功能即可,获取第i位的状态0-1,将第i位置1;使用unsigned char array实现
2、实现hash函数
#%% 自定义hash函数 class Bkdr_hash(object): def __init__(self, cap, seed): self.cap = cap self.seed = seed def bkdr_hashs(self, inputstr): ret = 0 for ch in inputstr: ret = ret*self.seed + ord(ch) # 控制输出的范围<cap return ret % self.cap
给出不同的种子,将string映射为整数,并且用%操作来控制输出值小于cap,即小于bitset的长度
hash的实现可以参考这篇: http://blog.csdn.net/mycomputerxiaomei/article/details/7641221
3、bloom过滤器实现
#%% 布隆过滤器 import math import BitVector as bv class Bloomfilter(object): def __init__(self, num=1e8, err=1e-6): self.num = num self.err = err # m/n的值,k=ln2*m/n,err=0.5^k self.n_hashs = int(math.log(self.err, 0.5)) self.bit_sz = int(self.n_hashs/math.log(2)*self.num) if 0: self.bitset = my_bitset(sz=self.bit_sz) # 我自己定义的数据结构 else: # 使用现成的 BitVector,实际上速度还没我的快 self.bitset = bv.BitVector(size=self.bit_sz) self.generate_hash_seeds() self.generate_hash_funs() def show(self): print 'n_hashs: %s, bit_sz: %s' % (self.n_hashs, self.bit_sz) ## 生成 self.n_hashs 个质数作为hash种子 def generate_hash_seeds(self): self.hash_seeds = [2] candi = 3 while len(self.hash_seeds)<self.n_hashs: flag = 1 for p in self.hash_seeds: if candi%p==0: flag = 0 break if flag: self.hash_seeds.append(candi) candi += 1 ## 产生hash函数 def generate_hash_funs(self): self.hash_funs = [] for se in self.hash_seeds: tmp_hf = Bkdr_hash(self.bit_sz, se).bkdr_hashs self.hash_funs.append(tmp_hf) ## 添加hash后的值 def add(self, value): for h_f in self.hash_funs: hashed_value = h_f(value) # print value, hashed_value self.bitset[hashed_value] = 1 ## 检查值是否在bloomfilter中 def __contains__(self, value): for h_f in self.hash_funs: hashed_value = h_f(value) # print hashed_value if self.bitset[hashed_value]==0: return False return True
根据误差和输入个数的计算公式,可以控制误差率
4、效果验证
首先定义一个随机字符串生成函数:
## 随机生成一些字符串 def generate_random_str(num): res = set() for i in range(num): length = random.randint(1,10) tmpv = '' for j in range(length): offset = random.randint(0,25) tmpv += chr(0x41+offset) res.add(tmpv) return res
随后给出测试函数:
def test_bloom_filter(): random.seed(0) bf = Bloomfilter(num=1e5, err=1e-3) bf.show() num = 10**4 # 将add集的元素加入bloomfilter addset = generate_random_str(num) # print testset for v in addset: bf.add(v) # 测试集,其中的元素都不在add集中 testset = generate_random_str(num) - addset st = time.clock() FP = 0 for v in testset: if v in bf: FP += 1 print FP, num, FP/float(num) print 'time cost:', time.clock()-st # 传统集合测试(由于实现的原因,传统集合比我这个快得多。。) FP = 0 st = time.clock() for v in testset: if v in addset: FP += 1 print FP, num, FP/float(num) print 'time cost:', time.clock()-st test_bloom_filter()
控制误差在千分之一,结果如下:
可以看到,使用9个hash函数来进行映射,存储1w个字符串,bit长度为1298425,大小为1298425.0/8/1024 = 158k
其误差率为0.0048,即10000个中有48个不在集合中,但被误判在集合内。
相比于python内建的set类型,我们的方法速度很慢,这个是由实现导致的。
hash的实现,每个字符串对应为一个8字节的信息指纹,大小为8*10000/1024*2 = 156k
注意,实际上我的bitset是按照容纳10**5个数来设计的,如果改为10**4,则容量bitset长度可以缩小一倍,同时误差率在2%左右
这样来看,用bloomfitler实现的set()容量仅为传统hashset的1/10左右
相关文章推荐
- 浅谈BloomFilter【上】基本概念和实现原理
- BloomFilter基本概念和实现原理
- P2P之UDP穿透NAT的原理与实现 [转]
- 基于Socket的聊天室实现原理
- P2P 之 UDP穿透NAT的原理与实现-转自CSDN[hBifTs]
- C# 2.0 中Iterators的改进与实现原理浅析
- 利用泛解析实现二级域名原理以及程序
- .NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析
- 基于IMD的包过滤防火墙原理与实现
- RunDll32 的使用方法与实现原理
- CLR 中匿名函数的实现原理浅析
- 使用临界段原理实现优化的进程间同步对象-原理和实现
- 自解压的jar实现原理
- C# 2.0 中Iterators的改进与实现原理浅析
- .NET 1.1中预编译ASP.NET页面实现原理浅析 [1] 自动预编译机制浅析
- gzip原理与实现
- 即时战略游戏中寻径(Path-finding)算法的原理及实现技术
- 深入分析基于VCL派生的ActiveX控件的实现原理及应用
- 求:Microsoft .Net Passport实现原理
- 游戏外挂的原理及实现