布隆过滤器用于集合成员资格,它确定一个元素是否存在于集合中。布隆过滤器是Burton H. Bloom于 1970 年在一篇名为Space/Time Trade-offs in Hash Coding with Allowable Errors (1970) 的论文中发明的。布隆过滤器是一种概率数据结构,适用于哈希编码方法(类似于 HashTable)。
我们什么时候需要布隆过滤器?
考虑以下任何一种情况:
- 假设我们有一个包含一些元素的列表,我们想检查给定的元素是否存在?
- 考虑到您正在开发电子邮件服务,并且您正在尝试使用给定用户名已存在或不存在的功能来实现注册端点?
- 假设您提供了一组列入黑名单的 IP,并且您想过滤掉某个给定的 IP 是否被列入黑名单?
如果没有布隆过滤器的帮助,这些问题能解决吗?
让我们尝试使用 HashSet 来解决这些问题
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args)
{
Set blackListedIPs
= new HashSet<>();
blackListedIPs.add("192.170.0.1");
blackListedIPs.add("75.245.10.1");
blackListedIPs.add("10.125.22.20");
// true
System.out.println(
blackListedIPs
.contains(
"75.245.10.1"));
// false
System.out.println(
blackListedIPs
.contains(
"101.125.20.22"));
}
}
true
false
为什么像 HashSet 或 HashTable 这样的数据结构会失败?
当我们的数据集有限时,HashSet 或 HashTable 效果很好,但可能不适合我们移动大型数据集。对于大型数据集,需要大量时间和大量内存。
类似数据结构的 HashSet 的数据集大小与插入时间
----------------------------------------------
|Number of UUIDs Insertion Time(ms) |
----------------------------------------------
|10 <1 |
|100 3 |
|1, 000 58 |
|10, 000 122 |
|100, 000 836 |
|1, 000, 000 7395 |
----------------------------------------------
类似数据结构的 HashSet 的数据集大小与内存(JVM 堆)
----------------------------------------------
|Number of UUIDs JVM heap used(MB) |
----------------------------------------------
|10 <2 |
|100 <2 |
|1, 000 3 |
|10, 000 9 |
|100, 000 37 |
|1, 000, 000 264 |
-----------------------------------------------
所以很明显,如果我们有大量数据,那么像 Set 或 HashTable 这样的普通数据结构是不可行的,这里 Bloom 过滤器就出现了。有关两者之间比较的更多详细信息,请参阅本文:Bloom filters 和 Hashtable 的区别
如何借助布隆过滤器解决这些问题?
让我们取一个大小为N的位数组(此处为 24)并用二进制零初始化每个位,现在取一些散列函数(您可以使用任意数量的散列函数,我们在这里使用两个散列函数进行说明)。
- 现在将您拥有的第一个 IP 传递给两个哈希函数,它会生成一些随机数,如下所示
hashFunction_1(192.170.0.1) : 2 hashFunction_2(192.170.0.1) : 6
现在,转到索引 2 和 6 并将该位标记为二进制 1。
- 现在传递您拥有的第二个 IP,并按照相同的步骤操作。
hashFunction_1(75.245.10.1) : 4 hashFunction_2(75.245.10.1) : 10
现在,转到索引 4 和 10 并将该位标记为二进制 1。
- 同样将第三个 IP 传递给两个散列函数,并假设您得到以下散列函数的输出
hashFunction_1(10.125.22.20) : 10 hashFunction_2(10.125.22.20) : 19
‘
现在,转到索引 10 和 19 并标记为二进制 1,这里索引 10 已经被前一个条目标记,因此只需将索引 19 标记为二进制 1。现在,是时候检查数据集中是否存在 IP,
- 测试输入 #1
假设我们要检查 IP 75.245.10.1 。使用我们为添加上述输入而采用的相同的两个哈希函数传递此 IP。hashFunction_1(75.245.10.1) : 4 hashFunction_2(75.245.10.1) : 10
现在,转到索引并检查位,如果索引 4 和 10 都用二进制 1 标记,则 IP 75.245.10.1存在于集合中,否则不包含在数据集中。
- 测试输入 #2
假设我们要检查 IP 75.245.20.30是否存在于集合中?所以过程将是相同的,使用我们为添加上述输入而采用的相同的两个哈希函数传递这个 IP。hashFunction_1(75.245.20.30) : 19 hashFunction_2(75.245.20.30) : 23
由于在索引 19 处设置为 1,但在索引 23 处设置为 0,因此我们可以说给定的 IP 75.245.20.30不存在于集合中。
为什么布隆过滤器是一种概率数据结构?
让我们再通过一个测试来理解这一点,这次考虑 IP 101.125.20.22并检查它是否存在于集合中。将此传递给两个哈希函数。考虑我们的哈希函数结果如下。
hashFunction_1(101.125.20.22) : 19
hashFunction_2(101.125.20.22) : 2
现在,访问设置为 1 的索引 19 和 2,它表示给定的 IP 101.125.20.22存在于集合中。
但是,这个 IP 101.125.20.22已经在数据集中进行了上面的处理,同时将 IP 添加到位数组。这被称为误报:
Expected Output: No
Actual Output: Yes (False Positive)
在这种情况下,索引 2 和 19 由其他输入设置为 1,而不是由这个 IP 101.125.20.22 设置。这就是所谓的碰撞,这就是为什么它是概率性的,发生的几率不是 100%。
对布隆过滤器有什么期望?
- 当布隆过滤器说一个元素不存在时,它肯定是不存在的。它保证 100% 给定的元素在集合中不可用,因为散列函数给出的索引位中的任何一个都将设置为 0。
- 但是当布隆过滤器说给定的元素存在时,它不是 100% 确定的,因为由于碰撞,散列函数给出的所有索引位已被其他输入设置为 1。
如何从布隆过滤器获得 100% 准确的结果?
好吧,这只能通过采用更多的哈希函数来实现。我们采用的散列函数数量越多,我们得到的结果就越准确,因为发生冲突的可能性越小。
布隆过滤器的时间和空间复杂度
假设我们有大约4000 万个数据集并且我们使用了大约H 哈希函数,那么:
Time complexity: O(H), where H is the number of hash functions used
Space complexity: 159 Mb (For 40 million data sets)
Case of False positive: 1 mistake per 10 million (for H = 23)
使用 Guava 库在Java实现布隆过滤器:
我们可以使用 Guava 提供的Java库来实现布隆过滤器。
- 包括以下 maven 依赖项:
com.google.guava guava 19.0 - 编写以下代码来实现布隆过滤器:
// Java program to implement // Bloom Filter using Guava Library import java.nio.charset.Charset; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; public class BloomFilterDemo { public static void main(String[] args) { // Create a Bloom Filter instance BloomFilter
blackListedIps = BloomFilter.create( Funnels.stringFunnel( Charset.forName("UTF-8")), 10000); // Add the data sets blackListedIps.put("192.170.0.1"); blackListedIps.put("75.245.10.1"); blackListedIps.put("10.125.22.20"); // Test the bloom filter System.out.println( blackListedIps .mightContain( "75.245.10.1")); System.out.println( blackListedIps .mightContain( "101.125.20.22")); } } 输出:
Note: The above Java code may return a 3% false-positive probability by default.
- 降低误报概率
在Bloom filter对象创建中引入另一个参数如下:
BloomFilter blackListedIps = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), 10000, 0.005);
BloomFilter blackListedIps = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), 10000, 0.005);
BloomFilter blackListedIps = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), 10000, 0.005);现在误报概率已从 0.03降低到 0.005 。但是调整这个参数会对布隆过滤器产生影响。
降低误报概率的效果:
让我们从哈希函数、数组位、时间复杂度和空间复杂度方面分析这种影响。
- 让我们看看不同数据集的插入时间。
----------------------------------------------------------------------------- |Number of UUIDs | Set Insertion Time(ms) | Bloom Filter Insertion Time(ms) | ----------------------------------------------------------------------------- |10 <1 71 | |100 3 17 | |1, 000 58 84 | |10, 000 122 272 | |100, 000 836 556 | |1, 000, 000 7395 5173 | ------------------------------------------------------------------------------
- 现在,让我们来看看内存(JVM 堆)
-------------------------------------------------------------------------- |Number of UUIDs | Set JVM heap used(MB) | Bloom filter JVM heap used(MB) | -------------------------------------------------------------------------- |10 <2 0.01 | |100 <2 0.01 | |1, 000 3 0.01 | |10, 000 9 0.02 | |100, 000 37 0.1 | |1, 000, 000 264 0.9 | ---------------------------------------------------------------------------
- 位数
---------------------------------------------- |Suggested size of Bloom Filter | Bit count | ---------------------------------------------- |10 40 | |100 378 | |1, 000 3654 | |10, 000 36231 | |100, 000 361992 | |1, 000, 000 3619846 | -----------------------------------------------
- 用于各种误报概率的哈希函数数量:
----------------------------------------------- |Suggested FPP of Bloom Filter | Hash Functions| ----------------------------------------------- |3% 5 | |1% 7 | |0.1% 10 | |0.01% 13 | |0.001% 17 | |0.0001% 20 | ------------------------------------------------
结论:
因此可以说布隆过滤器在我们需要处理大数据集且内存消耗低的情况下是一个不错的选择。此外,我们想要更准确的结果,必须增加哈希函数的数量。