一、特点
布隆过滤器是一种概率形的数据结构,通过布隆过滤器可以知道一个元素一定不存在或者可能存在。
优点:空间和时间都要远远超过一般算法。
缺点:会存在一定的误判,而且删除困难。
实质上就是由一个很长的二进制向量和一系列随机映射函数(Hash函数)组成的。
常见应用:网页黑名单系统、垃圾邮件过滤系统、爬虫的网址判重系统、解决缓存穿透问题。
二、原理
添加元素时候:记下每个元素经过哈希函数后的索引,将二进制向量上对应索引位置的元素都置为1。
查询元素时候:记下每个元素经过哈希函数后的索引,如果二进制向量上对应索引位置的元素都为1,就代表存在该元素(存在一定的误判率,可以见下图,图转自腾讯课堂小马哥)—误判本质就是对不同的元素使用系列哈希函数计算出来的某个结果是一样的即哈希冲突;如果都不为1,就代表该元素不存在(一定准确)。
三、误判率、数据规模、二进制向量长度、哈希函数个数之间的关系
m:二进制向量长度
p:误判率
n:数据规模
k:哈希函数个数
四、实现
public class BloomFilter<T> {
private int bitSize;
private long[] bits; //保存二进制向量
private int hashSize;
/**
* 布隆过滤器的构造函数
*
* @param numberSize 数据规模
* @param p 误判率
*/
public BloomFilter(int numberSize, double p) {
this.bitSize = (int) (-numberSize * Math.log(p) / Math.pow(Math.log(2), 2));
this.hashSize = (int) (bitSize * Math.log(2) / numberSize);
this.bits = new long[(int) Math.ceil(this.bitSize / 64)]; //向上取整
}
public boolean put(T value) {
int hashCode1 = value.hashCode();
int hashCode2 = hashCode1 >> 16;
boolean result = false;
for (int i = 0; i < hashSize; i++) {
int combinedHash = hashCode1 + (i * hashCode2);
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
int index = combinedHash % bitSize;
if (set(index)) {
result = true;
}
}
return result;
}
/**
* 设置二进制向量的index位为1, 设置成功返回true, 失败返回false
*/
private boolean set(int index) {
boolean result = false;
int numbersIndex = index / Long.SIZE;
int numberIndex = index % Long.SIZE;
//获得某个位的数字---只需要和该位数字为1其他位数字都为0的数进行按位与运算即可
result = (bits[numbersIndex] & (1 << numberIndex)) == 0; //如果取出的数字为0表示一定修改成功
//将某个位的数字设置为1---只需要和该位数字为1其他位数字都为0的数进行按位或运算即可
bits[numbersIndex] |= (1 << numberIndex);
return result;
}
public boolean contains(T value) {
int hashCode1 = value.hashCode();
int hashCode2 = hashCode1 >> 16;
for (int i = 0; i < hashSize; i++) {
int combinedHash = hashCode1 + (i * hashCode2);
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
int index = combinedHash % bitSize;
if (!get(index)) return false;
}
return true;
}
/**
* 看二进制向量的index位为是否为1
*/
private boolean get(int index) {
int numbersIndex = index / Long.SIZE;
int numberIndex = index % Long.SIZE;
return (bits[numbersIndex] & (1 << numberIndex)) != 0; //按位与取到的位不为0就位true
}
public static void main(String[] args) {
BloomFilter<Integer> bf = new BloomFilter<>(200_0000, 0.01);
for (int i = 0; i < 200_0000; i++) {
bf.put(i);
}
// for (int i = 0; i < 200_0000; i++) {
// System.out.println(bf.contains(i));
// }
int count = 0; //统计误判数量
for (int i = 200_0000; i < 400_0000; i++) {
if (bf.contains(i)) {
count++;
}
}
System.out.println(count);
}
}
运行结果:
PS:那用布隆过滤器解决缓存穿透问题思路其实也很简单,无非就是查询数据库将所有数据都放入布隆过滤器里面,因为布隆过滤器可以确保元素一定不存在的情况,避免直击数据库!