Python Counting Bloom Filter 原理与实现详细介绍
概述
Counting Bloom Filter 是 Bloom Filter 的升级版,除了具有 Bloom Filter 的高效性和空间节省性之外,还可以处理删除元素的问题。
这篇文章将详细介绍 Counting Bloom Filter 的原理、实现细节以及应用场景。
原理
Bloom Filter 简介
Bloom Filter 是一种空间效率非常高的随机数据结构,用于判断一个元素是否属于某个集合。它的核心思想是使用 k 个不同的哈希函数,将输入元素分别哈希成 m 位的二进制向量,并将向量中的每一位设置为 1。当需要查询某个元素是否在集合中时,将该元素经过哈希函数,检查它所对应的 m 位是否全部为 1。如果全部为 1,则认为该元素在集合中;如果存在任一位不为 1,则认为该元素不在集合中。
但是 Bloom Filter 也存在一定的缺点,由于所有的元素都被哈希到一张很小的表中,一旦 Bloom Filter 填满,就无法再添加新元素。而且 Bloom Filter 无法支持元素删除操作。
Counting Bloom Filter 原理
Counting Bloom Filter 稍作修改,它们并不使用简单的向量的位。相反,对于每个插入的元素,它们使用一个计数器。一旦所有元素都插入,则可以跟踪每个元素在数组中的出现次数。
当我们想要查询某个元素是否在 Counting Bloom Filter 中时,对应的哈希值被发送到元素的位置,此时计数器的值表示它已经被添加到 Counting Bloom Filter 中的次数。由于元素可能被计数器递减几次,所以 Counting Bloom Filter 不能被视为检测元素的绝对存在。
Counting Bloom Filter 对于查询某个元素是否存在的问题,可以保证不会产生误判,但会产生误报。
Counting Bloom Filter 实现
- 初始化
Counting Bloom Filter 的第一步是创建一个大小为 m 的计数器数组,其中每个计数器都是一个整数,并且值为 0。除此之外,还需要确定 k 个哈希函数。这提供了两个因素 - 首先,每个元素将被哈希至少一次;其次,尽管 Bloom Filter 是基于随机方法的,但是这些哈希函数仍然必须满足不同的互补性质。
- 添加元素
为了在 Counting Bloom Filter 中添加元素,每个元素的 k 个哈希值最好被插入到数组中,每个哈希对应的计数器都应该加 1。如果 Counting Bloom Filter 已经使用了所有元素,那么未出现的元素将是一个新的找到的位置。
- 删除元素
使用 Counting Bloom Filter 删除元素比插入更加复杂。如果从添加元素的数组中删除元素,那么如果有另一个元素而且它用相同的哈希值覆盖那么它会一起删除。意思是 Counting Bloom Filter 不能将计数器减少到负数。
为了解决这个问题,可以将 Counting Bloom Filter 的计数器替换为倍精度整数,这样它们可以支持减法。在删除指定元素时,将对应的 k 个哈希值的计数器相应地降低 1。
- 查询元素
查询元素时,只需将元素的哈希值发送到数组中。如果对应的 k 个计数器的值均大于 0,则表明该元素在 Counting Bloom Filter 内。
实例说明
示例 1: 去重
下面是一个使用 Counting Bloom Filter 去重的示例:
class CountingBloomFilter:
def __init__(self, m, k):
self.m = m
self.k = k
self.counters = [0] * m
self.hash_funcs = [hashlib.sha256(str(i).encode()).hexdigest() for i in range(k)]
def add(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
self.counters[index] += 1
def remove(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
if self.counters[index] > 0:
self.counters[index] -= 1
def __contains__(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
if self.counters[index] == 0:
return False
return True
if __name__ == "__main__":
cbf = CountingBloomFilter(100, 5)
urls = ["www.google.com", "www.baidu.com", "www.github.com", "www.google.com"]
for url in urls:
cbf.add(url)
print("www.google.com" in cbf) # True
print("www.tencent.com" in cbf) # False
在这个例子中,使用了 5 个哈希函数,数组大小为 100。添加了 4 个 url,但有一个 url 重复了。因为 Counting Bloom Filter 使用了计数器,因此不会将重复元素添加进去。在最后,容易发现 www.google.com 是在布隆过滤器中的。
示例 2: 防止重放攻击
下面是一个使用 Counting Bloom Filter 防止重放攻击的示例:
class CountingBloomFilter:
def __init__(self, m, k):
self.m = m
self.k = k
self.counters = [0] * m
self.hash_funcs = [hashlib.sha256(str(i).encode()).hexdigest() for i in range(k)]
def add(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
self.counters[index] += 1
def remove(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
if self.counters[index] > 0:
self.counters[index] -= 1
def __contains__(self, element):
for i in range(self.k):
index = int(hashlib.sha256(str(element + self.hash_funcs[i]).encode()).hexdigest(), 16) % self.m
if self.counters[index] == 0:
return False
return True
if __name__ == "__main__":
cbf = CountingBloomFilter(100, 5)
nonce = "123456"
message = "something"
# 先将 nonce 放入 Counting Bloom Filter 中
cbf.add(nonce)
# 第一次签名时,检查 nonce 是否存在
if nonce in cbf:
signature = sign(message)
# 第二次签名时检查 nonce 是否存在
if nonce in cbf:
signature = sign(message)
else:
raise Exception("Nonce has been used!")
在这个例子中,一个随机数被用作 nonce,它被放到 Counting Bloom Filter 中来防止重复签名。在第一次签名时,nonce 被检查是否存在,如果存在则签名成功。第二次签名时,再次检查 Counting Bloom Filter 中是否存在 nonce。如果存在就是重放攻击,签名将失败,并将引发异常。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python Counting Bloom Filter原理与实现详细介绍 - Python技术站