bloomfilter
文章目录
Bloom Filter
Scrapy-Redis的存储
Scrapy-Redis将request的指纹存储到Redis集合中,存储为长度为40,每一位都是16进制。
指纹:8297512a01c44b17b44b5281d2758ca23342e2d6
每个16进制数占4bit,1个指纹用40个16进制表示,占用空间为20Byte,1万个指纹占用空间200KB,1亿指纹占用1.86GB。Redis还存储了爬取队列,内存会进一步提高,当爬取到亿级别的规模,就要用更节省内存的去重算法Bloom Filter。
Bloom Filter算法
使用位数组来辅助检查,声明一个m位的位(bit)数组,所有位都是0。
Bloom Filter算法中,首先使用K个相互独立、随机的散列函数将x映射到长度为m的位数组上,将散列函数得到的结果记作位置索引,将位数组改位置变为1。我们用k个散列函数映射k个位置将对应的值变为1。
符合公式:m > n*k
- m:代表位数组的长度。
- n:n个集合元素
- k:k个散列函数
散列算法
class HashMap(object):
def __init__(self, m, seed):
self.m = m
self.seed = seed
def hash(self, value):
ret = 0
for i in range(len(value)):
ret += self.seed * ret + ord(ret[i])
return (self.m - 1) & ret
定义一个HashMap类,构造函数中,m表示数组的位数,seed表示种子的值,不用的散列函数要有不同的seed,这样散列函数才不会碰撞。
**hash()方法中:vaule要处理的内容,遍历value的每一位,并用ord求出对应的ASCII值,然后混洗seed进行迭代求和运算,最后得到一个值。
这个值由seed和value来确定。**我们将这个值和m进行位运算,就获取到m位数组的映射结果,这样就实现了由字符串和seed来确定的散列函数。
当m固定只要seed值相同,散列函数就是相同的,当seed不同散列函数就不同。
多个散列函数
当k个散列函数在一起就实现了Bloom Filter,我们对这几个散列函数指定相同的m不同的seed。
class BlommFilter:
def __init__(self, server, key, bit=30, hash_number=6):
self.m = 1 << bit
self.seeds = range(hash_number)
self.maps = [HashMap(self.m, seed) for seed in self.seeds]
self.server = server
self.key = key
- server:Redis连接对象
- key:键的名称
- bit:数组的位数
- hash_number:几个散列函数
当需要亿级别的数据,n为1亿的时候,散列函数k个数取10个左右。
m > n * k:m的值大约为10亿左右。
1 << bit:表示将1移位操作向左移动bit位,当bit位30的时候,相当2的30次方,等于1073741824,数量级在10亿,由于是位数组占用的大小为(10*9/8/1024/1024 = 128MB)大小。
开头Scrapy-Redis使用指纹来存储当有1亿指纹占用空间1.86G。
- self.m:将1向左进行30位移动
- self.seeds:获取6个种子值
- self.maps:构造不同seed值的HashMap对象,保存到数组中使用。
- self.server:Redis连接对象
- self.key:key的名称
接下来实现插入和判断重复的方法。
def exists(self, value):
if not value:
return false
exist = 1
for map in self.maps:
offset = map.hash(value)
exist = exist & self.server.getbit(self.key, offset)
return exist
def insert(self, value):
for f in self.maps:
offset = f.hash(value)
self.server.setbit(self.key, offset, 1)
insert方法
Bloom Filter算法逐个调用散列函数对放入集合中的元素进行运算,在得到m位位数组中映射的位置,将位数组对应的位置置为1。
我们通过for循环来遍历散列函数,然后调用hash()方法算出映射位置offset,再利用setbit()将该位置变为1。这里遍历了6个散列函数,就要有6个位从0变为1。
exists方法
定义一个exist置为1,然后遍历所有散列函数对value进行运算,得到映射位置,用getbit()方法获取映射位置的结果,和exist进行于运算。
只要有一次getbit()得到结果为0,即m位数组中有对应的0,那么最终结果exist就为False,表示集合中没有这个Value。
测试实例
from redis import StrictRedis
class HashMap:
def __init__(self, m, seed):
self.m = m
self.seed = seed
def hash(self, value):
ret = 0
for i in range(len(value)):
ret += self.seed * ret + ord(value[i])
return (self.m - 1) & ret
class BlommFilter:
def __init__(self, server, key, bit=30, hash_number=6):
self.m = 1 << bit # 移动的位数
self.seeds = range(hash_number)
self.maps = [HashMap(self.m, seed) for seed in self.seeds]
self.server = server
self.key = key
def exists(self, value):
if not value:
return False
exist = 1
for map in self.maps:
offset = map.hash(value)
print(offset)
exist = exist & self.server.getbit(self.key, offset)
return exist
def insert(self, value):
for f in self.maps:
offset = f.hash(value)
self.server.setbit(self.key, offset, 1)
redis = StrictRedis(host='localhost', port=6379)
bf = BlommFilter(redis, 'hash_map', 5, 1)
bf.insert('Hello')
bf.insert('World')
result = bf.exists('Hello')
print(bool(result))
result = bf.exists('Python')
print(bool(result))
运行结果为
11
8
True
False
[Finished in 0.3s]
我们查看Redis里面对应的key的值和类型,可知道类型为String。
127.0.0.1:6379> type hash_map
string
127.0.0.1:6379> get hash_map
"\x00\x90"
127.0.0.1:6379>
我们可以来计算一下,1向左移动了128位,有1个散列函数,能存128个元素。
我们看到offset偏移量为11和8,我们将第11位和第八位置为1。0000 0000 1001 0000
就是查询到的结果\x00\x90
,记得下次在Redis里计算结果的时候del一下,不然还会保存上次存储的结果。
setbit和getbit的用法
在redis中,存储的字符串都是二进制存在的。
设置一个key-value,键名叫demo值为’a’,'a’的ASCII码为97二进制就是0b0110 0001。
在二进制中每一位都是offest,在’a’中offest0=0,offest1=1,从左往右计数,也就是从高位往低位。
127.0.0.1:6379> set demo 'a'
OK
127.0.0.1:6379> get demo
"a"
接下来我们将’a’变为’b’用bit来操作,'b’的ASCII码为98对应的二进制就是0b01100010,就是将offset6=1,offset7=0。
127.0.0.1:6379> setbit demo 6 1
(integer) 0
127.0.0.1:6379> setbit demo 7 0
(integer) 1
127.0.0.1:6379> get demo
"b"
其中返回的integer就是没进行setbit之前的offset位的值。
误区
BlommFilter中在设置bit等于5时,并不是1向左移动5位,1向左移动的位数时 1<< bit后的。所以self.m=32移动了32位是31个位数组。
总结
就是调用多个散列函数来映射多个位,用其来构造Bloom Filter。
到最后在Redis存储的类型是String,以前指纹存储的时候是列表。
相关阅读
Bilateral Filters(双边滤波算法)原理及实现
双边滤波算法原理: 双边滤波是一种非线性滤波器,它可以达到保持边缘、降噪平滑的效果。和其他滤波原理一样,双边滤波也是采用加权平
我们在使用安全卫士、杀毒等产品扫描的时会出现蓝屏或是重启的问题,这是由于wimfilter.sys文件导致的,下面就和小编一起去看下wim
TOMCAT启动错误:严重: Error filterStart
在tomcat启动的时候报严重: Error filterStart这样的错误的原因有很多种,比如你在web.xml的配置语句写漏或写错或缺少某些jar包等
RePr Improved Training of Convolutional Filters阅
论文地址:https://arxiv.org/abs/1811.07275 CVPR2019的Oral Paper代码:作者目前没公布,Reddit上对于这篇论文的讨论很激烈┓( ´∀
概述 bilateralFilter()函数可以对图像进行双边滤波。 API说明 C++ API: void cv::bilateralFilter ( InputArray sr