引入依赖
com.google.guava
guava
26.0-jre
编写测试代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/** @author Created by 谭健 on 2019/9/25. 星期三. 10:47. © All Rights Reserved. */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BloomFilterTest {
/** 里面有多少个数据 */
private static final int EXPECT_SIZE = 100_0000;
/** 期望的误判率 */
private static final double FPP = 0.0000_5;
public static void main(String[] args) {
BloomFilter<Integer> filter = initFilter();
int count = 0;
for (int i = EXPECT_SIZE; i < EXPECT_SIZE * 2; i++) {
// 设置了一个循环,拿一些根本不存在的数据去测试,测试次数 = 总数据量
if (filter.mightContain(i)) {
count++;
log.info("当前数据 {} 不存在于过滤器中,但是返回存在,误判了。", i);
}
}
log.info("一共误判了 {} 个数据", count);
double v = count / (EXPECT_SIZE * 1.00);
log.info("误判率 {}", BigDecimal.valueOf(v).setScale(6, BigDecimal.ROUND_HALF_UP));
}
private static BloomFilter<Integer> initFilter() {
BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), EXPECT_SIZE, FPP);
for (int i = 0; i < EXPECT_SIZE; i++) {
filter.put(i);
}
return filter;
}
}
输出结果
11:09:24.703 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1043713 不存在于过滤器中,但是返回存在,误判了。
11:09:24.709 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1045293 不存在于过滤器中,但是返回存在,误判了。
11:09:24.716 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1084804 不存在于过滤器中,但是返回存在,误判了。
11:09:24.722 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1108827 不存在于过滤器中,但是返回存在,误判了。
11:09:24.731 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1170565 不存在于过滤器中,但是返回存在,误判了。
11:09:24.732 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1176553 不存在于过滤器中,但是返回存在,误判了。
11:09:24.733 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1181407 不存在于过滤器中,但是返回存在,误判了。
11:09:24.739 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1212393 不存在于过滤器中,但是返回存在,误判了。
11:09:24.740 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1216250 不存在于过滤器中,但是返回存在,误判了。
11:09:24.740 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1217755 不存在于过滤器中,但是返回存在,误判了。
11:09:24.755 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1302056 不存在于过滤器中,但是返回存在,误判了。
11:09:24.761 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1340223 不存在于过滤器中,但是返回存在,误判了。
11:09:24.762 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1345555 不存在于过滤器中,但是返回存在,误判了。
11:09:24.764 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1359757 不存在于过滤器中,但是返回存在,误判了。
11:09:24.765 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1362759 不存在于过滤器中,但是返回存在,误判了。
11:09:24.766 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1367631 不存在于过滤器中,但是返回存在,误判了。
11:09:24.767 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1372497 不存在于过滤器中,但是返回存在,误判了。
11:09:24.774 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1421526 不存在于过滤器中,但是返回存在,误判了。
11:09:24.778 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1452218 不存在于过滤器中,但是返回存在,误判了。
11:09:24.781 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1470719 不存在于过滤器中,但是返回存在,误判了。
11:09:24.798 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1583667 不存在于过滤器中,但是返回存在,误判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1586562 不存在于过滤器中,但是返回存在,误判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1587225 不存在于过滤器中,但是返回存在,误判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1588861 不存在于过滤器中,但是返回存在,误判了。
11:09:24.799 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1589613 不存在于过滤器中,但是返回存在,误判了。
11:09:24.801 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1605520 不存在于过滤器中,但是返回存在,误判了。
11:09:24.802 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1612489 不存在于过滤器中,但是返回存在,误判了。
11:09:24.802 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1614824 不存在于过滤器中,但是返回存在,误判了。
11:09:24.804 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1628491 不存在于过滤器中,但是返回存在,误判了。
11:09:24.805 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1636461 不存在于过滤器中,但是返回存在,误判了。
11:09:24.806 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1649258 不存在于过滤器中,但是返回存在,误判了。
11:09:24.808 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1670422 不存在于过滤器中,但是返回存在,误判了。
11:09:24.809 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1680673 不存在于过滤器中,但是返回存在,误判了。
11:09:24.810 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1684041 不存在于过滤器中,但是返回存在,误判了。
11:09:24.812 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1700898 不存在于过滤器中,但是返回存在,误判了。
11:09:24.816 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1737096 不存在于过滤器中,但是返回存在,误判了。
11:09:24.818 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1751971 不存在于过滤器中,但是返回存在,误判了。
11:09:24.819 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1757162 不存在于过滤器中,但是返回存在,误判了。
11:09:24.819 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1757183 不存在于过滤器中,但是返回存在,误判了。
11:09:24.820 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1764249 不存在于过滤器中,但是返回存在,误判了。
11:09:24.821 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1773561 不存在于过滤器中,但是返回存在,误判了。
11:09:24.822 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1784175 不存在于过滤器中,但是返回存在,误判了。
11:09:24.830 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1827745 不存在于过滤器中,但是返回存在,误判了。
11:09:24.835 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1865076 不存在于过滤器中,但是返回存在,误判了。
11:09:24.837 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1885160 不存在于过滤器中,但是返回存在,误判了。
11:09:24.840 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1911780 不存在于过滤器中,但是返回存在,误判了。
11:09:24.840 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1918022 不存在于过滤器中,但是返回存在,误判了。
11:09:24.844 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1944798 不存在于过滤器中,但是返回存在,误判了。
11:09:24.849 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1997282 不存在于过滤器中,但是返回存在,误判了。
11:09:24.849 [main] INFO com.example.demo.BloomFilterTest - 当前数据 1999119 不存在于过滤器中,但是返回存在,误判了。
11:09:24.850 [main] INFO com.example.demo.BloomFilterTest - 一共误判了 50 个数据
11:09:24.852 [main] INFO com.example.demo.BloomFilterTest - 误判率 0.000050
当然,误判率并不是完全准确的,设置的误判率,只是一个近似值。
此处引用 redisson 的依赖来实现
org.redisson
redisson
3.11.2
Redisson利用Redis实现了Java分布式布隆过滤器(Bloom Filter)。所含最大比特数量为2^32。
示例代码
RBloomFilter<SomeObject> bloomFilter = redisson.getBloomFilter("sample");
// 初始化布隆过滤器,预计统计元素数量为55000000,期望误差率为0.03
bloomFilter.tryInit(55000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));
基于集群的数据分片布隆过滤器Sharding
,不过基于集群的过滤器,仅限于Redisson PRO
版本中,需要收费
基于Redis的Redisson集群分布式布隆过滤器通过RClusteredBloomFilter接口,为集群状态下的Redis环境提供了布隆过滤器数据分片的功能。 通过优化后更加有效的算法,通过压缩未使用的比特位来释放集群内存空间。每个对象的状态都将被分布在整个集群中。所含最大比特数量为2^64。在这里可以获取更多的内部信息。
示例代码
RClusteredBloomFilter<SomeObject> bloomFilter = redisson.getClusteredBloomFilter("sample");
// 采用以下参数创建布隆过滤器
// expectedInsertions = 255000000
// falseProbability = 0.03
bloomFilter.tryInit(255000000L, 0.03);
bloomFilter.add(new SomeObject("field1Value", "field2Value"));
bloomFilter.add(new SomeObject("field5Value", "field8Value"));
bloomFilter.contains(new SomeObject("field1Value", "field8Value"));
代码如下:
import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/** @author Created by 谭健 on 2019/9/25. 星期三. 11:29. © All Rights Reserved. */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JedisBloomFilter {
/** 里面有多少个数据 */
private static final int EXPECT_SIZE = 1_0000;
/** 期望的误判率 */
private static final double FPP = 0.0005;
/** bit数组长度 */
private static final long NUM_BITS;
/** hash函数数量 */
private static final int NUM_HASH_FUNCTIONS;
/** redis 中的key */
private static final String KEY = "JedisBloomFilter:TEST";
private static final Jedis JEDIS;
static {
// 计算bit数组长度
NUM_BITS = (long) (-EXPECT_SIZE * Math.log(FPP) / (Math.log(2) * Math.log(2)));
// 计算hash函数个数
NUM_HASH_FUNCTIONS =
Math.max(1, (int) Math.round((double) NUM_BITS / EXPECT_SIZE * Math.log(2)));
// 初始化JEDIS
JEDIS = new Jedis();
JEDIS.auth("caishang");
JEDIS.select(10);
}
public static void main(String[] args) {
// init
for (int i = 0; i < EXPECT_SIZE; i++) {
long[] longs = getIndex(String.valueOf(i));
for (long index : longs) {
JEDIS.setbit(KEY, index, true);
}
}
// 检测
int count = 0;
for (int i = EXPECT_SIZE; i < EXPECT_SIZE * 2; i++) {
if (mightContain(i)) {
count++;
log.info("当前数据 {} 不存在于过滤器中,但是返回存在,误判了。", i);
}
}
log.info("一共误判了 {} 个数据", count);
double v = count / (EXPECT_SIZE * 1.00);
log.info("误判率 {}", BigDecimal.valueOf(v).setScale(6, BigDecimal.ROUND_HALF_UP));
}
/**
* 判断某个数据是否可能存在于过滤器中
*
* @return true = 可能存在
*/
private static boolean mightContain(Object i) {
long[] longs = getIndex(String.valueOf(i));
for (long index : longs) {
boolean isContain = JEDIS.getbit(KEY, index);
// 有任意一个不存在,则该值一定不存在
if (!isContain) {
return false;
}
}
return true;
}
/** 根据key获取bitmap下标 */
private static long[] getIndex(String key) {
long hash1 = hash(key);
long[] result = new long[NUM_HASH_FUNCTIONS];
for (int i = 0; i < NUM_HASH_FUNCTIONS; i++) {
long combinedHash = hash1 + i * (hash1 >>> 16);
if (combinedHash < 0) {
combinedHash = ~combinedHash;
}
result[i] = combinedHash % NUM_BITS;
}
return result;
}
private static long hash(String key) {
Charset charset = StandardCharsets.UTF_8;
return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong();
}
}
运行结果如下:
12:49:41.605 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 11360 不存在于过滤器中,但是返回存在,误判了。
12:49:41.628 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 11579 不存在于过滤器中,但是返回存在,误判了。
12:49:41.703 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 12403 不存在于过滤器中,但是返回存在,误判了。
12:49:41.769 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 13118 不存在于过滤器中,但是返回存在,误判了。
12:49:41.785 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 13290 不存在于过滤器中,但是返回存在,误判了。
12:49:41.807 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 13553 不存在于过滤器中,但是返回存在,误判了。
12:49:42.064 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 16564 不存在于过滤器中,但是返回存在,误判了。
12:49:42.129 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 17295 不存在于过滤器中,但是返回存在,误判了。
12:49:42.183 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 17920 不存在于过滤器中,但是返回存在,误判了。
12:49:42.243 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 18628 不存在于过滤器中,但是返回存在,误判了。
12:49:42.297 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 19255 不存在于过滤器中,但是返回存在,误判了。
12:49:42.355 [main] INFO com.example.demo.JedisBloomFilter - 当前数据 19972 不存在于过滤器中,但是返回存在,误判了。
12:49:42.357 [main] INFO com.example.demo.JedisBloomFilter - 一共误判了 12 个数据
12:49:42.359 [main] INFO com.example.demo.JedisBloomFilter - 误判率 0.001200
与设置误判率有一定的差距,但是不大。
使用该方式作为过滤器时,效率不太高。本代码仅仅作为示例,没有进行改良,10000个数据在Redis 中就已经很大了。