布隆过滤器

判断一个字符串存不存在,可以用来过滤非法字符串、垃圾邮件等

1.使用BigSet存放hash位置信息

  • 初始化定义存放位置size,如4、8、16、32

  • 有多少个size就需要多少次hash,将得到的hash值作为索引存放在set中,value为true

2.判断是否存在,即将该字符串做hash处理,判断Bigset每个位置都为true,那么就认定该字符串存在

3.内存回收,当set占满,即每个字符串判断时都会存在,所以不能占满。
可以设置为百分之80,清空该set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package com.onedesk.dsp.utils;

import java.io.*;
import java.util.BitSet;
import java.util.concurrent.atomic.AtomicInteger;

public class BloomFilter implements Serializable {
private static final long serialVersionUID = -5221305273707291280L;
private final int[] seeds;
private final int size;
private final BitSet notebook;
private final MisjudgmentRate rate;
private final AtomicInteger useCount = new AtomicInteger(0);
private final Double autoClearRate;

/**
* 默认中等程序的误判率:MisjudgmentRate.MIDDLE 以及不自动清空数据(性能会有少许提升)
*
* @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
*/
public BloomFilter(int dataCount) {
this(MisjudgmentRate.MIDDLE, dataCount, 0.8);
}

/**
* @param rate 一个枚举类型的误判率
* @param dataCount 预期处理的数据规模,如预期用于处理1百万数据的查重,这里则填写1000000
* @param autoClearRate 自动清空过滤器内部信息的使用比率,传null则表示不会自动清理,
* 当过滤器使用率达到100%时,则无论传入什么数据,都会认为在数据已经存在了
* 当希望过滤器使用率达到80%时自动清空重新使用,则传入0.8
*/
public BloomFilter(MisjudgmentRate rate, int dataCount, Double autoClearRate) {
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;
notebook = new BitSet(size);
this.autoClearRate = autoClearRate;
}

public void add(String data) {
checkNeedClear();

for (int i = 0; i < seeds.length; i++) {
int index = hash(data, seeds[i]);
setTrue(index);
}
}

public boolean check(String data) {
for (int i = 0; i < seeds.length; i++) {
int index = hash(data, seeds[i]);
if (!notebook.get(index)) {
return false;
}
}
return true;
}

/**
* 如果不存在就进行记录并返回false,如果存在了就返回true
*
* @param data
* @return
*/
public boolean addIfNotExist(String data) {
checkNeedClear();

int[] indexs = new int[seeds.length];
// 先假定存在
boolean exist = true;
int index;

for (int i = 0; i < seeds.length; i++) {
indexs[i] = index = hash(data, seeds[i]);

if (exist) {
if (!notebook.get(index)) {
// 只要有一个不存在,就可以认为整个字符串都是第一次出现的
exist = false;
// 补充之前的信息
for (int j = 0; j <= i; j++) {
setTrue(indexs[j]);
}
}
} else {
setTrue(index);
}
}

return exist;

}

private void checkNeedClear() {
if (autoClearRate != null) {
if (getUseRate() >= autoClearRate) {
synchronized (this) {
if (getUseRate() >= autoClearRate) {
notebook.clear();
useCount.set(0);
}
}
}
}
}

public void setTrue(int index) {
useCount.incrementAndGet();
notebook.set(index, true);
}

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);
}

public double getUseRate() {
return (double) useCount.intValue() / (double) size;
}


/**
* 清空过滤器中的记录信息
*/
public void clear() {
useCount.set(0);
notebook.clear();
}

public MisjudgmentRate getRate() {
return rate;
}

/**
* 分配的位数越多,误判率越低但是越占内存
* <p>
* 4个位误判率大概是0.14689159766308
* <p>
* 8个位误判率大概是0.02157714146322
* <p>
* 16个位误判率大概是0.00046557303372
* <p>
* 32个位误判率大概是0.00000021167340
*
* @author lianghaohui
*/
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;

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(100000);
System.out.println(fileter.addIfNotExist("1111111111111"))
}
}