1.bitMap(位图)
bitMap中用一位代表一个值 常见应用在数据去重
bitMap空间复杂度不会随着数据量的增大而增大
bitMap空间复杂度会随要存储的数据元素的最大值线性增大
比如存 ‘4’ 会直接存在第4位 那么64bit类型的数据 最大值为2^64 那么要存到bitMap中 就要找到第2^64次方比特位存这个数 就需要
2^64bit=2EB空间 和天文数字一样不现实。
hashMap也可以字典去重 但是存1个数需要4byte 也就是4*8=32bit 而bitMap中一个数只占一个bit位 所以bitMap更加省空间。
jdk中就有一个实现 bitSet 用过的都知道 bitSet的构造函数传入的容量为int类型 而不是long 原因如上。所以面对海量数据需要大量的空间时bitMap就不合适了
2.布隆过滤器
布隆过滤器是对hash表以及bitMap的一种极致应用
1.首先创建一个bitMap 其中所有元素置为0
2.创建多个hash函数 每存一个数就通过这几个hash函数映射到指定的位置并置为1,比如有8个hash函数就说明 8个bit位代表一个数
3.判断一个数是否存在 就通过这几个hash函数找到指定位置 其中只要有1位为0那么就不存在,都为1那么这个数可能存在。
由于大量数据一般处于分布式环境中所以这里准备的是基于redis的布隆过滤器。(redis中有位图的实现)
1.redis工具类
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置bitMap
*
* @param key bitMap key
* @param offset 设置在多少位
* @param flag 设置的值
*/
public void setBit(String key, Long offset, Boolean flag) {
redisTemplate.opsForValue().setBit(key, offset, flag);
}
/**
* 获取bitMap指定位置的值
*
* @param key bitMap key
* @param offset 位置
*/
public Boolean getBit(String key, Long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
}
2.建立hash函数所需要的seed
public enum CorrectRatio {
/**
* 32位代表一个数
*/
HIGH(new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131});
private int[] seed;
CorrectRatio(int[] size) {
this.seed = size;
}
public int[] getSeed() {
return seed;
}
public void setSeed(int[] seed) {
this.seed = seed;
}
}
3.布隆过滤器
public class BoolmfilterForRedis {
@Autowired
private RedisUtil redisUtil;
/**
* BKRD Hash算法seed
*/
private int[] seed;
/**
* hash方法 数组
*/
private HashFunction[] hashFunction;
/**
* 所需bitMap 位数 为2的幂次方
*/
private Long cap;
private static final String bitMapPrefix = "bitMapForBoolmfilter";
/**
* 初始化容量 redis中位图的容量
*
* @param n 输入量
*/
private void initCap(CorrectRatio correctRatio, Long n) {
/*
* 如果correctRatio.getSeed().length 为32 即有32个hash函数对一个数做映射
* 那么 bitSet中 32位代表一个数 所需要的最大容量为32*n
*/
this.cap = correctRatio.getSeed().length * n;
}
/**
* 构造 hash函数
*
* @param correctRatio
*/
private void createHashFunctions(CorrectRatio correctRatio) {
this.seed = correctRatio.getSeed();
this.hashFunction = new HashFunction[this.seed.length];
for (int i = 0; i < seed.length; i++) {
this.hashFunction[i] = new HashFunction(seed[i], this.cap);
}
}
/**
* 初始化
*
* @param correctRatio
* @param n
*/
public BoolmfilterForRedis(CorrectRatio correctRatio, Long n) {
initCap(correctRatio, n);
createHashFunctions(correctRatio);
}
/**
* 添加
*
* @param value
*/
public void add(String value) {
for (int i = 0; i < seed.length; i++) {
redisUtil.setBit(bitMapPrefix, this.hashFunction[i].hash(value), true);
}
}
/**
* 包含
*
* @param value
* @return
*/
public Boolean contain(String value) {
boolean flag = true;
for (int i = 0; i < seed.length; i++) {
flag = flag && redisUtil.getBit(bitMapPrefix, this.hashFunction[i].hash(value));
if (!flag) {
return false;
}
}
return flag;
}
/**
* hash算法
*/
private class HashFunction {
private Integer seed;
private Long cap;
public HashFunction(Integer seed, Long cap) {
this.seed = seed;
this.cap = cap;
}
public Long hash(String value) {
Integer result = 0;
for (int i = 0; i < value.length(); i++) {
result = result * this.seed + value.charAt(i);
}
//求余数 防止redis中bitMap无限膨胀 类似循环队列 可以防止系统OOM
return result & (cap - 1);
}
}
}
4.由于初始化不能通过无参构造函数 所以还要配置
@Configuration
public class BoolmfilterConfig {
@Value("${boolmfilter.inputnum.pow}")
private Integer pow;
@Bean
public BoolmfilterForRedis getBoolmfilter(){
//输入数据量cap=输入数据量 * hash个数
// hash个数就是seed数 所以要求seed个数为2的幂次方 输入量也要是2的幂次方 详情参考hashMap容量为什么为2的幂次方
BoolmfilterForRedis boolmfilter=new BoolmfilterForRedis(CorrectRatio.HIGH,(long)1<<pow);
return boolmfilter;
}
}
5.测试
测试前准备1000uuid存入
@RunWith(SpringRunner.class)
@SpringBootTest
public class GatewayApplicationTests {
@Autowired
private BoolmfilterForRedis boolmfilter;
@Autowired
private RedisTemplate redisTemplate;
@Test
public void contextLoads() {
//1.先存入1000个uuid
for (int i=0;i<1000;i++) {
redisTemplate.opsForList().rightPush("xx", UUID.randomUUID().toString());
}
}
}
2.将数据填入boolmfilter
List<String> xx = redisTemplate.opsForList().range("bitMapForBoolmfilter", 0L, 100L);
xx.forEach(s -> {
boolmfilter.add(s);
});
3.测试通过率 由于uuid基本不重复 所以这里生成的uuid通过率在不误判的情况下应为0
int count = 0;
for (int i = 0; i < 10000; i++) {
boolean contains = boolmfilter.contain(UUID.randomUUID().toString());
if (contains == true) {
count++;
}
}
System.out.println(count);
Boolean contain = boolmfilter.contain("6cce2977-1bc2-4e19-9e9a-2485ff6b7950");
System.out.println(contain);