当需要判断一个对象是否在千万级以上的集合时利用BloomFilter是非常有用的。
Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。
即为通过如下checkExists返回为true则不一定在这个集合中返回false一定不在集合中,原理即为给定一个对象之后进行n次hash然后根据每次hash出的值进行在区域里面打点,判定一个对象时hash之后然后打点看下所有打点都存在值是否命中,
即一个人说在集合中不一定在所有人都说在也不一定在,只要有人说不在则一定不在。即为push时把每个对象hash多次,相当于加密,查询存在时将查找的值hash多次,如果多次hash的值扔在集合中则则不一定在集合中,存在冲突的情况,
如果多次hash之后又一次不在集合中则一定不在集合中。
如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。
Hash面临的问题就是冲突。假设 Hash 函数是良好的,如果我们的位阵列长度为 m 个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳 m/100 个元素。显然这就不叫空间有效了(Space-efficient)。解决方法也简单,就是使用多个 Hash,如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,虽然也有一定可能性它们在说谎,不过直觉上判断这种事情的概率是比较低的。
哈希函数的概念是:将任意大小的数据转换成特定大小的数据的函数,转换后的数据称为哈希值或哈希编码。
public class BloomFilter<T> implements Serializable {
private static final long serialVersionUID = 1L;
protected byte[] flags; //hash 数据
protected int size; //hash 元素个数
protected int bitsize; //hash位数
protected transient Murmur2 hashFunction;
protected int hashCount; //hash函数个数
protected int[] seeds; //hash 函数seed
/**
* Returns a new BloomFilter object.
*
* @param hashCount
* Number of hash functions.
* @param size
* Bloom filter size <b>in bits</b>. If this parameter is < 8,
* then the filter will have the size of 8 bits. Otherwise the
* size will be (size / 8) * 8 bits.
* @param hashCount
* An object extending the IntHashFunctionFactory
* (Murmur2Factory, for instance) which supplies the hash
* functions.
*/
public BloomFilter(int hashCount, int size) {
init();
setupSizes(hashCount, size);
}
public BloomFilter() {
init();
}
public void init() {
hashFunction = new Murmur2();
}
/**
* Returns a size of a filter in <b>bytes</b>
*
* @return Size of the filter in bytes.
*/
public Integer getSize() {
return this.size;
}
/**
* Returns a size of a filter in <b>bits</b>
*
* @return Size of the filter in bits
*/
public Integer getBitSize() {
return this.bitsize;
}
/**
* Converts hash value into corresponding bit number
*
* @param hash
* Int hash value
* @return Index of corresponding bit
*/
protected long getIndex(int hash) {
long hl = (long) hash;
hl += (long) Integer.MAX_VALUE;
hl %= bitsize;
return hl;
}
/**
* Gets actual flags array index by bit number
*
* @param bigIdx
* Bit number
* @return Corresponding index in flags array
*/
protected long getBitIndex(long bigIdx) {
return (bigIdx >> 3);
}
/**
* Gets bit by bit number
*
* @param bigIdx
* Bit number
* @return Bit mask
*/
protected byte getBitMask(long bigIdx) {
return (byte) (1 << (bigIdx % 8));
}
/**
* Converts hashes[index] value into index and mask and sets an appropriate
* bit in flags
*
* @param hashes
* Hash values array
* @param index
* Index of current hash in a loop
*/
protected void doProcessAddElement(int[] hashes, int index) {
long idx = getIndex(hashes[index]);
int cidx = (int)getBitIndex(idx);
byte mask = getBitMask(idx);
flags[cidx] |= mask;
}
/**
* Converts hashes[index] value into index and mask and tests if an
* appropriate bit is set
*
* @param hashes
* Hash values array
* @param index
* Index of current hash in a loop
* @return <code>true</code> if corresponding bit is set, otherwise
* <code>false</code>
*/
protected boolean doProcessCheckElement(int[] hashes, int index) {
long idx = getIndex(hashes[index]);
int cidx = (int)getBitIndex(idx);
byte mask = getBitMask(idx);
return ((flags[cidx] & mask) != 0);
}
/**
* Adds an element to the filter. Hashes are calculated based on the
* element.toString() value.
*
* 我们去掉了同步代码,这个函数应该在初始化阶段调用, 后续不能再调用
*
* @param element
* An element to add
*/
public void put(T element) {
int[] h = hash(element.toString().getBytes());
for (int i = 0; i < hashCount; i++) {
doProcessAddElement(h, i);
}
}
/**
* 检查元素是否在集合中,
* 我们去掉了同步代码, 因此不要与put 混合使用
* @param element
* @return
*/
public boolean checkExists(T element) {
boolean result = true;
int[] h = hash(element.toString().getBytes());
for (int i = 0; i < hashCount; i++) {
result &= doProcessCheckElement(h, i);
}
return result;
}
public double getQuality(long storageSize) {
double x = Math.pow(1.0 - 1.0 / (double) bitsize,
(double) (hashCount * storageSize));
double q = Math.pow(1 - x, hashCount);
return 1 - q; // 1 - probability of false positive
}
protected int[] hash(byte[] data) {
int[] result = new int[hashCount];
for (int i = 0; i < hashCount; i++) {
if (i == 0)
result[i] = hashFunction.hash(data, 87914052 ^ seeds[i]);
else
result[i] = hashFunction.hash(data, result[i - 1] ^ seeds[i]);
}
return result;
}
protected void setupSizes(int hashCount, int size) {
if (size >= 8)
this.size = size >> 3;
else
this.size = 1;
this.bitsize = this.size << 3;
this.hashCount = hashCount;
flags = new byte[this.size];
seeds = new int[hashCount];
for (int i = 0; i<hashCount; i++)
seeds[i] = (int)Math.round(Math.random()*1000000)+1;
}
public static void main(String[] args) {
BloomFilter<String> filter = new BloomFilter<String>(10, 200000000);
System.out.println(filter.getQuality(4200000));
filter.put("zhangsan");
filter.put("wangwu");
System.out.println(filter.checkExists("wangwu"));
System.out.println(filter.checkExists("wang"));
BloomFilter<String> filter2 = new BloomFilter<String>(10, 100000000);
System.out.println(filter2.getQuality(2500000));
}
}
public class Murmur2 {
/**
* Returns the Murmur2 (32-bit) hash value of the supplied data.
*
* @param data Data that needs to be hashed in form of byte array.
* @param seed Seed for hash function
* @return Integer hash value
*/
public int hash(byte[] data, int seed) {
int m = 0x5bd1e995;
int r = 24;
int len = data.length;
int h = seed ^ len;
int i = 0;
while (len >= 4) {
int k = data[i + 0] & 0xFF;
k |= (data[i + 1] & 0xFF) << 8;
k |= (data[i + 2] & 0xFF) << 16;
k |= (data[i + 3] & 0xFF) << 24;
k *= m;
k ^= k >>> r;
k *= m;
h *= m;
h ^= k;
i += 4;
len -= 4;
}
switch (len) {
case 3:
h ^= (data[i + 2] & 0xFF) << 16;
case 2:
h ^= (data[i + 1] & 0xFF) << 8;
case 1:
h ^= (data[i + 0] & 0xFF);
h *= m;
}
h ^= h >>> 13;
h *= m;
h ^= h >>> 15;
return h;
}
}