布隆过滤器
布隆过滤器
一、定义
布隆过滤器:1970年由布隆提出,它是一个空间效率高的概率型数据结构,可以用来告诉你:一个元素一定存在或者可能存在。
优缺点:
优点:空间效率和查询时间都远远超过一般的算法。
缺点:有一定的误判率,删除困难。
本质是:一个很长的二进制向量和一系列随机映射函数(Hash函数)
二、应用场景
- 网页黑名单系统,垃圾邮件过滤系统,爬虫的网址判重系统,解决缓存穿透问题。
- 解决缓存穿透问题的原理:将所有key值存储到布隆过滤器中,请求到达先经过布隆过滤器,布隆过滤器说数据不存在,则一定不存在,返回不存在即可。布隆过滤器说存在,则可能存在,去数据库查询即可。可通过布隆过滤器,解决缓存穿透的问题。
三、布隆过滤器原理
* 假设布隆过滤器由20位二进制,3个哈希函数组成,每个元素经过哈希函数处理都能生成一个索引位置。
* 添加元素:将每个哈希函数生成的索引位置都设为一。
- 查询元素是否存在
果有一个哈希函数生成的索引位置不为1,这代表不存在(100%);
如果每一个哈希函数生成的索引位置都为1,就代表存在(存在误判);
* 原理图
四、时间空间复杂度
- 添加查询的时间复杂度都是:0(k),k是哈希函数的个数。空间复杂度是:O(m),m是二进制的个数。
五、正确性证明
- 布隆过滤器的正确性,由严格的数学证明,具体可参考相关文献,这里重在应用。
- 下面给出结论:p误判率、m二进制位的个数、k哈希函数的个数、n数据规模。
六:代码实现
/**
* @author 韩俊强
* @Description 布隆过滤器
* @createTime 2021年06月23日 10:20:00
*/
public class BloomFilter <T>{
// 二进制向量的长度(一共有多少位二进制)
private int bitSize;
// 二进制向量
private long[] bits;
// 哈希函数的个数
private int hashSize;
/**
* 构造函数
* @param n 数据规模
* @param p 误判率
* @author 韩俊强
* @date: 2021/6/23 10:24
*/
public BloomFilter(int n, double p) {
if (n <= 0 || p <= 0 || p >= 1){
throw new IllegalArgumentException("wrong n or p");
}
// 套用公式计算 bitSize hashSize
double ln2 = Math.log(2);
bitSize = (int)(-(n * Math.log(p))/(ln2 * ln2));
hashSize =(int)(bitSize * ln2 /n);
// bits数组的长度
bits = new long[(bitSize + Long.SIZE - 1)/ Long.SIZE];
}
/**
* 添加元素
* @param 要添加的元素
* @return:bits是否发送了修改
* @author 韩俊强
*/
public boolean put(T value){
nullCheck(value);
// 具体怎么实现的求索引,可用现有实现。这里是测试,不具备生产环境使用。
int hash1 = value.hashCode( ) ;
int hash2 = hash1 >>> 16;
boolean result = false;
for (int i = 1; i <= hashSize; i++) {
int combinedHash = hash1 + (i * hash2) ;
if ( combinedHash < 0) {
combinedHash = ~ combinedHash;
}
// 生成一个二进制索引
int index = combinedHash % bitSize;
if (set(index)) {
result = true;
}
}
return result;
}
/**
* 判断元素是否存在
* @param value
* @return:是否包含元素
* @author 韩俊强
*/
public boolean contains( T value){
nullCheck(value);
// 具体怎么实现的求索引,可用现有实现。这里是测试,不具备生产环境使用。
int hash1 = value.hashCode( ) ;
int hash2 = hash1 >>> 16;
for (int i = 1; i <= hashSize; i++) {
int combinedHash = hash1 + (i * hash2) ;
if ( combinedHash < 0) {
combinedHash = ~ combinedHash;
}
// 生成一个二进制索引
int index = combinedHash % bitSize;
if (!get(index)){
return false;
}
}
return true;
}
/**
*
* @param index
* @return:是否发送修改
* @author 韩俊强
*/
private boolean set(int index){
long value = bits[index/Long.SIZE];
int bitValue = 1 << (index % Long.SIZE);
bits[index/Long.SIZE] = value | bitValue;
return (value & bitValue) == 0;
}
/**
*
* @param index
* @return:是否为1
* @author 韩俊强
*/
private boolean get(int index){
long value = bits[index/Long.SIZE];
return (value = value & (1 << (index % Long.SIZE))) != 0;
}
private void nullCheck(T value){
if (value == null){
throw new IllegalArgumentException("value must not be null");
}
}
// 不提供删除功能,不适合。强行实现思路,可以使用计数的方式,删除则对应位--;
}