关于布隆过滤器的定义以及应用场景这里不再赘述, 直接上实现.
属性
解释并定义关于布隆过滤器的几个属性:
- n: 数据总量(比如有100亿个URL)
- p: 允许的失误率, 由于哈希冲突可能导致的误杀(某个url没被加入黑名单却被拦截)
- m: 布隆过滤器的大小, 由n和p计算得到的值, 公式: m = -(n*lnp)/(ln2)^2,证明过程略
- k: 需要的hash映射过程数, 由n和p得到, 公式: k = ln2*(m/n)
- bitMap: 布隆过滤器存储数据的地方, 这里采用long[]类型, 由于数组的最大容量是Integer.MAX_VALUE, 所以整个过滤器的最大容量为64*Integer.MAX_VALUE, 64为long类型所占bit数, 即一次hash映射只改变bitMap中的一个bit.
private long n; //数据总量
private double p; //允许失误率
private long m; //布隆过滤器大小 m = -(n*lnp)/(ln2)^2
private int k; //哈希函数个数 k = ln2*(m/n)
private long[] bitMap; //数据域 选用long[]意味着m最大为64*Integer.MAX_VALUE
private static final String[] HASH_ALGORITHM = {
"MD5", "SHA1", "MD2", "SHA256", "SHA384", "SHA512", "SHA224"
};//Hash算法名称
构造函数
由n和p计算出m、k的值, 并初始化bitMap
public BloomFilter(long n, double p) {
this.n = n;
this.p = p;
this.m = -(long) Math.ceil(n * Math.log(p) / Math.pow(Math.log(2), 2));
this.k = (int) (Math.log(2) * (this.m / n));
this.bitMap = new long[(int) Math.ceil(this.m / 64.0)];
}
位运算
用于将一次hash映射的结果操作到bitMap中
/**
* 将bitMap中指定位 置1
*
* @param index 偏移量
*/
public void setBit(long index) {
int idx = (int) index / 64;
int offSet = (int) index % 64;
this.bitMap[idx] |= (long) 1 << (64 - offSet);
}
用于判断bitMap中某一位是否被set过
/**
* 判断指定位置是否被set过
*
* @param index 偏移量
* @return
*/
public boolean getBit(long index) {
int idx = (int) index / 64;
int offSet = (int) index % 64;
return (this.bitMap[idx] & 1L << (64 - offSet)) != 0;
}
put和contains方法
/**
* 将hash算法得到的byte[]转为long类型的在bitMap中的偏移量
*
* @param bytes
* @return
*/
public long changeByte2LongIndex(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return Math.abs(buffer.getLong() % this.m);
}
/**
* 向过滤器放数据
*
* @param url
* @throws Exception
*/
public void put(String url) throws Exception {
for (int i = 0; i < k && i < HASH_ALGORITHM.length; i++) {
byte[] digest = MessageDigest.getInstance(HASH_ALGORITHM[i]).digest(url.getBytes());
long index = changeByte2LongIndex(digest);
setBit(index);
}
}
/**
* 判断数据是否在过滤器中
*
* @param url
* @return
*/
public boolean contains(String url) throws Exception {
for (int i = 0; i < k && i < HASH_ALGORITHM.length; i++) {
byte[] digest = MessageDigest.getInstance(HASH_ALGORITHM[i]).digest(url.getBytes());
long index = changeByte2LongIndex(digest);
if (!getBit(index)) return false;
}
return true;
}
完整代码
import java.nio.ByteBuffer;
import java.security.MessageDigest;
/**
* 布隆过滤器
*/
public class BloomFilter {
private long n; //数据总量
private double p; //允许失误率
private long m; //布隆过滤器大小 m = -(n*lnp)/(ln2)^2
private int k; //哈希函数个数 k = ln2*(m/n)
private long[] bitMap; //数据域 选用long[]意味着m最大为64*Integer.MAX_VALUE
private static final String[] HASH_ALGORITHM = {
"MD5", "SHA1", "MD2", "SHA256", "SHA384", "SHA512", "SHA224"
};//Hash算法名称
public BloomFilter() {
}
public BloomFilter(long n, double p) {
this.n = n;
this.p = p;
this.m = -(long) Math.ceil(n * Math.log(p) / Math.pow(Math.log(2), 2));
this.k = (int) (Math.log(2) * (this.m / n));
this.bitMap = new long[(int) Math.ceil(this.m / 64.0)];
}
/**
* 将bitMap中指定位 置1
*
* @param index 偏移量
*/
public void setBit(long index) {
int idx = (int) index / 64;
int offSet = (int) index % 64;
this.bitMap[idx] |= (long) 1 << (64 - offSet);
}
/**
* 判断指定位置是否被set过
*
* @param index 偏移量
* @return
*/
public boolean getBit(long index) {
int idx = (int) index / 64;
int offSet = (int) index % 64;
return (this.bitMap[idx] & 1L << (64 - offSet)) != 0;
}
/**
* 将hash算法得到的byte[]转为long类型的在bitMap中的偏移量
*
* @param bytes
* @return
*/
public long changeByte2LongIndex(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return Math.abs(buffer.getLong() % this.m);
}
/**
* 向过滤器放数据
*
* @param url
* @throws Exception
*/
public void put(String url) throws Exception {
for (int i = 0; i < k && i < HASH_ALGORITHM.length; i++) {
byte[] digest = MessageDigest.getInstance(HASH_ALGORITHM[i]).digest(url.getBytes());
long index = changeByte2LongIndex(digest);
setBit(index);
}
}
/**
* 判断数据是否在过滤器中
*
* @param url
* @return
*/
public boolean contains(String url) throws Exception {
for (int i = 0; i < k && i < HASH_ALGORITHM.length; i++) {
byte[] digest = MessageDigest.getInstance(HASH_ALGORITHM[i]).digest(url.getBytes());
long index = changeByte2LongIndex(digest);
if (!getBit(index)) return false;
}
return true;
}
public static void main(String[] args) {
BloomFilter filter = new BloomFilter(20000, 0.001);
try {
filter.put("www.baidu.com");
System.out.println(filter.contains("www.baidu.com"));
} catch (Exception e) {
e.printStackTrace();
}
}
}