一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
布隆过滤器简介
- 布隆过滤器是一个很长的二进制向量和一系列随机映射函数。它可以用于检索一个元素是否在一个集合中。
- 优点:空间效率和查询时间都远远超过一般的算法。
- 缺点:有一定的误识别率和删除困难。
插入与查询
- 布隆过滤器无论是在空间,还是时间方面都有着非常大的优势。因为它的存储空间和插入、查询时间都是 级别的。同时为了方便由硬件并行实现,所设计的散列函数之间不能相关联。
- 当元素数量比较少时,就可以使用散列表。但是随着存入的元素数量增加,误算率就会随之增加。而且我们不能够从布隆过滤器中删除元素。
参数与推导公式结果
- 假定布隆过滤器长度(二进制位个数)为 m,散列函数个数 k ,误判率 p ,数据规模 n 。
- 公式推到结果为: , 。
代码现实
public class BloomFilter<T> {
/**
* 二进制数向量长度
* */
private int bit_size;
/**
* 二进制数向量
* */
private long[] bits;
/**
* 散列函数个数
* */
private int hash_size;
public BloomFilter(int n, double p) {
if (n <= 0 || p <= 0 || p >= 1) {
throw new IllegalArgumentException("param error!");
}
double ln2 = Math.log(2);
double lnp = Math.log(p);
bit_size = -(int)(n * lnp / ln2 * ln2); // m
hash_size = -(int)(lnp / ln2); // k
bits = new long[bit_size / Long.SIZE + (bit_size % Long.SIZE == 0 ? 0 : 1)];
}
public boolean put(T value) {
if (value == null) return false;
int hash1 = value.hashCode();
int hash2 = hash1 >>> 16;
boolean ans = false;
for (int i = 1; i <= hash_size; i++) {
int mix_hash = hash1 + (i * hash2);
if (mix_hash < 0) {
mix_hash = ~mix_hash;
}
int index = mix_hash % bit_size;
if (set(index)) {
ans = true;
}
}
return ans;
}
public boolean contains(T value) {
if (value == null) return false;
int hash1 = value.hashCode();
int hash2 = hash1 >>> 16;
for (int i = 1; i <= hash_size; i++) {
int mix_hash = hash1 + (i * hash2);
if (mix_hash < 0) mix_hash = ~mix_hash;
int index = mix_hash % bit_size;
if (!get(index)) {
return false;
}
}
return true;
}
private boolean set(int index) {
long value = bits[index / Long.SIZE];
int bit_value = 1 << (index % Long.SIZE);
bits[index / Long.SIZE] = value | bit_value;
return (value & bit_value) == 0;
}
private boolean get(int index) {
long value = bits[index / Long.SIZE];
int bit_value = 1 << (index % Long.SIZE);
return (value & bit_value) != 0;
}
}
代码测试结果与分析
- 由以下测试结果显示,误判率是比较接近我们设置的误判率 的 。如果想要生产环境中使用的话,就可以考虑 Google 实现的布隆过滤器 点击这里 。当然在 redis 中也是有实现好的布隆过滤器。