布隆过滤器的简单实现
原理: 布隆过滤器是采用 BitMap 来实现的, 大致原理是: 将系统中所有存在的 key 经过 hash 运行后放入 bitmap, 在查询之前先通过 bitmap 过滤掉一定不存在的 key。
缺点是: 因为有 hash 冲突的问题, 所以通过布隆过滤器的数据也有可能是系统中不存在的 key, 但是没有通过布隆过滤器的 key 一定是系统中不存在的。
package com.xtyuns.common;
@FunctionalInterface
public interface HashFunction {
long hash(String str);
}
package com.xtyuns.common;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
@Component
public class RedisBloomFilter {
@Autowired
private StringRedisTemplate redisTemplate;
static final int DEFAULT_SIZE = 1 << 14; // aka 16k
static final int[] DEFAULT_SEEDS = {31, 73, 101, 127, 179, 223};
/**
* 返回给定数字的 2 的次幂形式。
*/
static int alignSize(int size) {
int n = -1 >>> Integer.numberOfLeadingZeros(size - 1);
return (n < 0) ? 1 : (n == Integer.MAX_VALUE) ? Integer.MAX_VALUE : n + 1;
}
/**
* 根据数据容量和散列种子数组来构造散列函数列表
*/
static HashFunction[] createHahFunctions(int alignedSize, int[] seeds) {
HashFunction[] functions = new HashFunction[seeds.length];
for (int i = 0; i < seeds.length; i++) {
final int finalI = i;
functions[i] = str -> {
int seed = seeds[finalI], h = 0;
for (byte v : str.getBytes(StandardCharsets.UTF_8)) {
h = seed * h + (v & 0xff);
}
return h & alignedSize - 1;
};
}
return functions;
}
/**
* 散列函数列表
*/
transient HashFunction[] hashFunctions;
/**
* 存放在 redis 中的数据容器的键名称
*/
final String BLOOM_KEY_IN_REDIS = "bitmap:redis_bloom_filter";
/**
* 根据指定的容量和散列种子数组构造 RedisBloomFilter
*
* @param size 数据容器的规模
* @param seeds 散列函数列表的种子
* @throws IllegalArgumentException 如果数据规模为负数或散列种子数组为空时
*/
public RedisBloomFilter(int size, int ... seeds) {
if (size < 0)
throw new IllegalArgumentException("无效的容器空间: " + size);
if (null == seeds || seeds.length == 0)
throw new IllegalArgumentException("散列种子数组不能为空");
this.hashFunctions = createHahFunctions(alignSize(size), seeds);
}
/**
* 根据指定的容量和默认散列种子数组构造 RedisBloomFilter
* @param size 数据容器的大小
* @throws IllegalArgumentException 如果数据容量为负数时
*
*/
public RedisBloomFilter(int size) {
this(size, DEFAULT_SEEDS);
}
/**
* 使用默认参数构造 RedisBloomFilter
*/
public RedisBloomFilter() {
this(DEFAULT_SIZE, DEFAULT_SEEDS);
}
/**
* 检查 IOC 容器是否成功注入 redis 操作对象
*/
@PostConstruct
public void checkProperties() {
if (null == this.redisTemplate) throw new RuntimeException("redis 初始化失败!");
}
/**
* 判断给定的字符串数据是否可能已存在, 布隆过滤器可能存在误判
*/
public boolean mightContains(String str) {
for (HashFunction hashFunction : this.hashFunctions) {
long hash = hashFunction.hash(str);
Boolean bit = redisTemplate.opsForValue().getBit(BLOOM_KEY_IN_REDIS, hash);
if (null == bit || !bit) return false;
}
return true;
}
/**
* 向数据容器中添加给定的字符串数据
*/
public void add(String str) {
for (HashFunction hashFunction : this.hashFunctions) {
long hash = hashFunction.hash(str);
redisTemplate.opsForValue().setBit(BLOOM_KEY_IN_REDIS, hash, true);
}
}
}