布隆过滤器
薛定谔see猫 2021/4/30 算法数据结构
# 1. 简介
布隆过滤器Bloom Filter
:是一个空间效率高的概率型数据结构,可以确保一个元素一定不存在或者可能存在
优缺点
- 优点:空间效率和查询时间都远远超过一般的算法
- 缺点:有一定的误判率、删除困难
原理
本质上是一个很长的二进制向量和一系列随机映射函数Hash
函数
假设布隆过滤器由20位二进制,3个哈希函数组成,每个元素经过哈希函数处理都能生成一个索引位置
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
添加元素:将每一个哈希函数计算出的索引位置都设置为1
假设要插入
A
,经hash1
计算的出索引为2
,经hash2
计算的出索引为7
,经hash3
计算的出索引为18
则有0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 再插入一个
B
,经hash1
计算的出索引为2
,经hash2
计算的出索引为7
,经hash3
计算的出索引为15
则有0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 0 1 0 0 0 0 1 0 0 0 0 0 0 01 0 0 1 0 查询利用哈希函数生成索引,如果所有的索引都为1,则返回
true
,否则返回false
添加、查询的时间复杂度都是
O(K)
,K
是哈希函数的个数
# 2.误判率
影响误判率的三个因素影响:二进制位的个数
m
、哈希函数的个数k
、数据规模n
$$ P=(1-e^{-\frac{k(n+0.5)}{m-1}})^k $$ 可以简写为 $$ P=(1-e^{-\frac{kn}{m}})^k $$已知误判率
p
,数据规模n
,求二进制的个数m
、哈希函数的个数k
$$ m=-\frac{nlnp}{(ln2)^2},k=\frac{m}{n}ln2,k=-\frac{lnp}{ln2}=-log_2p $$
# 3. 具体实现
# 3.1 类的结构
public class BloomFilter<T> {
private int bitSize; // 二进制向量的长度
// 二进制向量
private long[] bits;
private int hashSize; // 哈希函数的个数
// n数据规模
// 误判率
public BloomFilter(int n, double p) {
}
// 添加元素
public boolean put(T value) {
return false;
}
// 判断一个元素是否存在
public boolean contains(T value) {
return true;
}
// 设置index位置的二进制位为1
private void set(int index) {
}
// 获取index位置的二进制位是否为0
private boolean get(int index) {
}
private void nullCheck(T value) {
}
}
# 3.2 方法实现
构造函数
public BloomFilter(int n, double p) { if (n <= 0 || p <= 0 || p>= 1) { throw new IllegalArgumentException("参数错误"); } double ln2 = Math.log(2); bitSize = (int)(-(n * Math.log(p)) / (ln2 * ln2)); // 求解二进制向量的长度 hashSize = (int) (bitSize * ln2 / n); // 哈希函数的个数 bits = new long[(bitSize + Long.SIZE - 1) / Long.SIZE]; // 计算bits数组的长度 System.out.println(bitSize + " " + hashSize); }
添加元素
public boolean put(T value) { nullCheck(value); // 利用value生成两个整数 int hash1 = value.hashCode(); // 获取value的hashcode int hash2 = hash1 >>> 16; // 获取value的hashcode for (int i = 0; i < hashSize; i++) { int combinedHash = hash1 + (i * hash2); if (combinedHash < 0) { combinedHash = ~combinedHash; } int index = combinedHash % bitSize; // 求取索引 // 设置index位置为1 set(index); } return false; }
判断一个元素是否存在
// 判断一个元素是否存在 public boolean contains(T value) { nullCheck(value); // 利用value生成两个整数 int hash1 = value.hashCode(); // 获取value的hashcode int hash2 = hash1 >>> 16; // 获取value的hashcode for (int i = 0; i < hashSize; i++) { int combinedHash = hash1 + (i * hash2); if (combinedHash < 0) { combinedHash = ~combinedHash; } int index = combinedHash % bitSize; // 求取索引 // 查看index位置的二进制位是否为0 if (get(index)) return false; } return true; }
设置
index
位置的二进制位为1private void set(int index) { bits[index / Long.SIZE] = bits[index / Long.SIZE] | (1 << (index % Long.SIZE)); }
检查
index
位置的二进制位是否为0// true代表1,false代表0 private boolean get(int index) { return (bits[index / Long.SIZE] & (1 << (index % Long.SIZE))) != 0; }
空值检测
private void nullCheck(T value) { if (value == null) { throw new IllegalArgumentException("元素不能为空"); } }