bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。
算法:
1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。
优点:不需要存储key,节省空间;缺点:1. 算法判断key在集合中时,有一定的概率key其实不在集合中 2. 无法删除。
有一个长度为m的bit型数组,如我们所知,每个位置只占一个bit,每个位置只有0和1两种状态。假设一共有k个哈希函数相互独立,输入域都为s且都大于等于m,那么对同一个输入对象(可以想象为缓存中的一个key),经过k个哈希函数计算出来的结果也都是独立的。对算出来的每一个结果都对m取余,然后在bit数组上把相应的位置设置为1(描黑),如下图所示:
布隆过滤器的误判率
假设 Hash 函数以等概率条件选择并设置 Bit Array 中的某一位,m 是该位数组的大小,k 是 Hash 函数的个数,那么位数组中某一特定的位在进行元素插入时的 Hash 操作中没有被置位的概率是:
那么在所有 k 次 Hash 操作后该位都没有被置 "1" 的概率是:
如果我们插入了 n 个元素,那么某一位仍然为 "0" 的概率是:
因而该位为 "1"的概率是:
现在检测某一元素是否在该集合中。标明某个元素是否在集合中所需的 k 个位置都按照如上的方法设置为 "1",但是该方法可能会使算法错误的认为某一原本不在集合中的元素却被检测为在该集合中(False Positives),该概率由以下公式确定:
布隆过滤器的大小m公式:
哈希函数的个数k公式:
布隆过滤器真实失误率p公式:
假设缓存系统,key为userId,value为user。如果我们有10亿个用户,规定失误率不能超过0.01%,通过计算器计算可得m=19.17n,向上取整为20n,也就是需要200亿个bit,换算之后所需内存大小就是2.3G。通过第二个公式可计算出所需哈希函数k=14.因为在计算m的时候用了向上取整,所以真实的误判率绝对小于等于0.01%。
布隆过滤器可以使用google已经实现的jar
-
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
-
<dependency>
-
<groupId>com.google.guava
</groupId>
-
<artifactId>guava
</artifactId>
-
<version>25.1-jre
</version>
-
</dependency>
Java实现简单的布隆过滤器
-
//https://blog.csdn.net/u014653197/article/details/76397037
-
public
class
BloomFilter
implements
Serializable{
-
-
private final
int[] seeds;
-
private final
int size;
-
private final BitSet notebook;
-
private final MisjudgmentRate rate;
-
private final AtomicInteger useCount =
new AtomicInteger();
-
private final Double autoClearRate;
-
-
//dataCount逾预期处理的数据规模
-
public BloomFilter(int dataCount){
-
this(MisjudgmentRate.MIDDLE, dataCount,
null);
-
}
-
-
//自动清空过滤器内部信息的使用比率,传null则表示不会自动清理;
-
//当过滤器使用率达到100%时,则无论传入什么数据,都会认为在数据已经存在了;
-
//当希望过滤器使用率达到80%时自动清空重新使用,则传入0.8
-
public BloomFilter(MisjudgmentRate rate, int dataCount, Double autoClearRate){
-
//每个字符串需要的bit位数*总数据量
-
long bitSize = rate.seeds.length * dataCount;
-
if(bitSize<
0 || bitSize>Integer.MAX_VALUE){
-
throw
new RuntimeException(
"位数太大溢出了,请降低误判率或者降低数据大小");
-
}
-
this.rate = rate;
-
seeds = rate.seeds;
-
size = (
int)bitSize;
-
//创建一个BitSet位集合
-
notebook =
new BitSet(size);
-
this.autoClearRate = autoClearRate;
-
}
-
-
//如果存在返回true,不存在返回false
-
public boolean addIfNotExist(String data){
-
//是否需要清理
-
checkNeedClear();
-
//seeds.length决定每一个string对应多少个bit位,每一位都有一个索引值
-
//给定data,求出data字符串的第一个索引值index,如果第一个index值对应的bit=false说明,该data值不存在,则直接将所有对应bit位置为true即可;
-
//如果第一个index值对应bit=true,则将index值保存,但此时并不能说明data已经存在,
-
//则继续求解第二个index值,若所有index值都不存在则说明该data值不存在,将之前保存的index数组对应的bit位置为true
-
int[] indexs =
new
int[seeds.length];
-
//假定data已经存在
-
boolean exist =
true;
-
int index;
-
for(
int i=
0; i<seeds.length; i++){
-
//计算位hash值
-
indexs[i] = index = hash(data, seeds[i]);
-
if(exist){
-
//如果某一位bit不存在,则说明该data不存在
-
if(!notebook.
get(index)){
-
exist =
false;
-
//将之前的bit位置为true
-
for(
int j=
0; j<=i; j++){
-
setTrue(indexs[j]);
-
}
-
}
-
}
else{
-
//如果不存在则直接置为true
-
setTrue(index);
-
}
-
}
-
-
return exist;
-
}
-
-
private int hash(String data, int seeds) {
-
char[]
value = data.toCharArray();
-
int hash =
0;
-
if(
value.length>
0){
-
for(
int i=
0; i<
value.length; i++){
-
hash = i * hash +
value[i];
-
}
-
}
-
hash = hash * seeds % size;
-
return Math.abs(hash);
-
}
-
-
private void setTrue(int index) {
-
useCount.incrementAndGet();
-
notebook.
set(index,
true);
-
}
-
-
//如果BitSet使用比率超过阈值,则将BitSet清零
-
private void checkNeedClear() {
-
if(autoClearRate !=
null){
-
if(getUseRate() >= autoClearRate){
-
synchronized (
this) {
-
if(getUseRate() >= autoClearRate){
-
notebook.clear();
-
useCount.
set(
0);
-
}
-
}
-
}
-
}
-
}
-
-
private Double getUseRate() {
-
return (
double)useCount.intValue()/(
double)size;
-
}
-
-
public void saveFilterToFile(String path) {
-
try (ObjectOutputStream oos =
new ObjectOutputStream(
new FileOutputStream(path))) {
-
oos.writeObject(
this);
-
}
catch (Exception e) {
-
throw
new RuntimeException(e);
-
}
-
-
}
-
-
public static BloomFilter readFilterFromFile(String path) {
-
try (ObjectInputStream ois =
new ObjectInputStream(
new FileInputStream(path))) {
-
return (BloomFilter) ois.readObject();
-
}
catch (Exception e) {
-
throw
new RuntimeException(e);
-
}
-
}
-
-
/**
-
* 清空过滤器中的记录信息
-
*/
-
public void clear() {
-
useCount.
set(
0);
-
notebook.clear();
-
}
-
-
public MisjudgmentRate getRate() {
-
return rate;
-
}
-
-
/**
-
* 分配的位数越多,误判率越低但是越占内存
-
*
-
* 4个位误判率大概是0.14689159766308
-
*
-
* 8个位误判率大概是0.02157714146322
-
*
-
* 16个位误判率大概是0.00046557303372
-
*
-
* 32个位误判率大概是0.00000021167340
-
*
-
*/
-
public
enum MisjudgmentRate {
-
// 这里要选取质数,能很好的降低错误率
-
/**
-
* 每个字符串分配4个位
-
*/
-
VERY_SMALL(
new
int[] {
2,
3,
5,
7 }),
-
/**
-
* 每个字符串分配8个位
-
*/
-
SMALL(
new
int[] {
2,
3,
5,
7,
11,
13,
17,
19 }),
//
-
/**
-
* 每个字符串分配16个位
-
*/
-
MIDDLE(
new
int[] {
2,
3,
5,
7,
11,
13,
17,
19,
23,
29,
31,
37,
41,
43,
47,
53 }),
//
-
/**
-
* 每个字符串分配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[] seeds;
-
-
//枚举类型MIDDLE构造函数将seeds数组初始化
-
private MisjudgmentRate(int[] seeds) {
-
this.seeds = seeds;
-
}
-
-
public int[] getSeeds() {
-
return seeds;
-
}
-
-
public void setSeeds(int[] seeds) {
-
this.seeds = seeds;
-
}
-
}
-
-
public static void main(String[] args) {
-
BloomFilter fileter =
new BloomFilter(
7);
-
System.
out.println(fileter.addIfNotExist(
"1111111111111"));
-
System.
out.println(fileter.addIfNotExist(
"2222222222222222"));
-
System.
out.println(fileter.addIfNotExist(
"3333333333333333"));
-
System.
out.println(fileter.addIfNotExist(
"444444444444444"));
-
System.
out.println(fileter.addIfNotExist(
"5555555555555"));
-
System.
out.println(fileter.addIfNotExist(
"6666666666666"));
-
System.
out.println(fileter.addIfNotExist(
"1111111111111"));
-
//fileter.saveFilterToFile("C:\\Users\\john\\Desktop\\1111\\11.obj");
-
//fileter = readFilterFromFile("C:\\Users\\john\\Desktop\\111\\11.obj");
-
System.
out.println(fileter.getUseRate());
-
System.
out.println(fileter.addIfNotExist(
"1111111111111"));
-
}
-
-
}
构造布隆过滤器,指定hash函数个数k,分配bit数组,计算给定字符串K个hash函数值,将每一位置为true,不考虑误判率,只有当k位bit都为true时,才表示给定字符串已经存在。