通過前一篇文章的學習,對於 BloomFilter 的概念和原理,以及誤報率等計算方法都一個理性的認識了。在這裏,我們將用 Java'實現一個簡單的 BloomFilter 。
package pri.xiaoye.day1029;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.BitSet;
import java.util.Collection;
/**
* 布隆過濾器的實現類
* 定義:http://en.wikipedia.org/wiki/Bloom_filter
*
* @param <E>
* E指定了要插入過濾器中的元素的類型,eg,String Integer
* @author Magnus Skjegstad <[email protected]>
* @translator xiaoye
*/
public class BloomFilter<E> implements Serializable {
private static final long serialVersionUID = -2326638072608273135L;
private BitSet bitset;
private int bitSetSize;
private double bitsPerElement;
private int expectedNumberOfFilterElements;//能夠添加的元素的最大個數
private int numberOfAddedElements;//過濾器容器中元素的實際數量
private int k; // 哈希函數的個數
static final Charset charset = Charset.forName("UTF-8");//存儲哈希值的字符串的編碼方式
static final String hashName = "MD5"; //在大多數情況下,MD5提供了較好的散列精確度。如有必要,可以換成 SHA1算法
static final MessageDigest digestFunction;//MessageDigest類用於爲應用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法
static { // 初始化 MessageDigest 的摘要算法對象
MessageDigest tmp;
try {
tmp = java.security.MessageDigest.getInstance(hashName);
} catch (NoSuchAlgorithmException e) {
tmp = null;
}
digestFunction = tmp;
}
/**
* 構造一個空的布隆過濾器. 過濾器的長度爲c*n
* @param c
* 表示每個元素佔有多少位
* @param n
* 表示過濾器能添加的最大元素數量
* @param k
* 表示需要使用的哈希函數的個數
*/
public BloomFilter(double c, int n, int k) {
this.expectedNumberOfFilterElements = n;
this.k = k;
this.bitsPerElement = c;
this.bitSetSize = (int) Math.ceil(c * n);
numberOfAddedElements = 0;
this.bitset = new BitSet(bitSetSize);
}
/**
* 構造一個空的布隆過濾器。最優哈希函數的個數將由過濾器的總大小和期望元素個數來確定。
*
* @param bitSetSize
* 指定了過濾器的總大小
* @param expectedNumberOElements
* 指定了過濾器能添加的最大的元素數量
*/
public BloomFilter(int bitSetSize, int expectedNumberOElements) {
this(bitSetSize / (double) expectedNumberOElements, expectedNumberOElements, (int) Math.round((bitSetSize / (double) expectedNumberOElements)* Math.log(2.0)));
}
/**
* 通過指定誤報率來構造一個過濾器。
* 每個元素所佔的位數和哈希函數的數量會根據誤報率來得出。
*
* @param falsePositiveProbability
* 所期望誤報率.
* @param expectedNumberOfElements
* 要添加的元素的數量
*/
public BloomFilter(double falsePositiveProbability, int expectedNumberOfElements) {
this(Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2)))/ Math.log(2), // c = k/ln(2)
expectedNumberOfElements,
(int) Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2)))); // k = ln(2)m/n
}
/**
* 根據舊過濾器的數據,重新構造一個新的過濾器
*
* @param bitSetSize
* 指定了過濾器所需位的大小
* @param expectedNumberOfFilterElements
* 指定了過濾器所能添加的元素的最大數量
* to contain.
* @param actualNumberOfFilterElements
* 指定了原來過濾器的數據的數量
* <code>filterData</code> BitSet.
* @param filterData
* 原有過濾器中的BitSet對象
*/
public BloomFilter(int bitSetSize, int expectedNumberOfFilterElements,
int actualNumberOfFilterElements, BitSet filterData) {
this(bitSetSize, expectedNumberOfFilterElements);
this.bitset = filterData;
this.numberOfAddedElements = actualNumberOfFilterElements;
}
/**
* 根據字符串的內容生成摘要
*
* @param val
* 字符串的內容
* @param charset
* 輸入數據的編碼方式
* @return 輸出爲一個long類型
*/
public static long createHash(String val, Charset charset) {
return createHash(val.getBytes(charset));
}
/**
* 根據字符串內容生成摘要
*
* @param val
* 指定了輸入的字符串。默認的編碼爲 UTF-8
* @return 輸出爲一個long類型
*/
public static long createHash(String val) {
return createHash(val, charset);
}
/**
* 根據字節數組生成摘要
*
* @param data
* 輸入數據
* @return 輸出爲long類型的摘要
*/
public static long createHash(byte[] data) {
long h = 0;
byte[] res;
synchronized (digestFunction) {
res = digestFunction.digest(data);
}
for (int i = 0; i < 4; i++) {
h <<= 8;
h |= ((int) res[i]) & 0xFF;
}
return h;
}
/**
* 重寫equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BloomFilter<E> other = (BloomFilter<E>) obj;
if (this.expectedNumberOfFilterElements != other.expectedNumberOfFilterElements) {
return false;
}
if (this.k != other.k) {
return false;
}
if (this.bitSetSize != other.bitSetSize) {
return false;
}
if (this.bitset != other.bitset
&& (this.bitset == null || !this.bitset.equals(other.bitset))) {
return false;
}
return true;
}
/**
* 重寫了hashCode方法
*
*/
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + (this.bitset != null ? this.bitset.hashCode() : 0);
hash = 61 * hash + this.expectedNumberOfFilterElements;
hash = 61 * hash + this.bitSetSize;
hash = 61 * hash + this.k;
return hash;
}
/**
* 根據最大元素數量和過濾器的大小來計算誤報率。
* 方法的返回值爲誤報率。如果插入的元素個數小於最大值,則誤報率會比返回值要小。
*
* @return 期望的誤報率.
*/
public double expectedFalsePositiveProbability() {
return getFalsePositiveProbability(expectedNumberOfFilterElements);
}
/**
* 通過插入的元素數量和過濾器容器大小來計算實際的誤報率。
*
* @param numberOfElements
* 插入的元素的個數.
* @return 誤報率.
*/
public double getFalsePositiveProbability(double numberOfElements) {
// (1 - e^(-k * n / m)) ^ k
return Math.pow((1 - Math.exp(-k * (double) numberOfElements
/ (double) bitSetSize)), k);
}
/**
* 通過實際插入的元素數量和過濾器容器大小來計算實際的誤報率。
*
* @return 誤報率.
*/
public double getFalsePositiveProbability() {
return getFalsePositiveProbability(numberOfAddedElements);
}
/**
* 返回哈希函數的個數 k
*
* @return k.
*/
public int getK() {
return k;
}
/**
* 清空過濾器元素
*/
public void clear() {
bitset.clear();
numberOfAddedElements = 0;
}
/**
* 向過濾器中添加元素。
* 添加的元素的toString()方法將會被調用,返回的字符串作爲哈希函數的輸出。
*
* @param element
* 要添加的元素
*/
public void add(E element) {
long hash;
String valString = element.toString();
for (int x = 0; x < k; x++) {
hash = createHash(valString + Integer.toString(x));
hash = hash % (long) bitSetSize;
bitset.set(Math.abs((int) hash), true);
}
numberOfAddedElements++;
}
/**
* 添加一個元素集合到過濾器中
*
* @param c
* 元素集合.
*/
public void addAll(Collection<? extends E> c) {
for (E element : c)
add(element);
}
/**
* 用來判斷元素是否在過濾器中。如果已存在,返回 true。
*
* @param element
* 要檢查的元素.
* @return 如果估計該元素已存在,則返回true
*/
public boolean contains(E element) {
long hash;
String valString = element.toString();
for (int x = 0; x < k; x++) {
hash = createHash(valString + Integer.toString(x));
hash = hash % (long) bitSetSize;
if (!bitset.get(Math.abs((int) hash)))
return false;
}
return true;
}
/**
* 判斷一個集合中的元素是否都在過濾器中。
*
* @param c
* 要檢查的元素集合
* @return 如果集合所有的元素都在過濾器中,則返回true。
*/
public boolean containsAll(Collection<? extends E> c) {
for (E element : c)
if (!contains(element))
return false;
return true;
}
/**
* 得到某一位的值
*
* @param bit
* bit的位置.
* @return 如果該位被設置,則返回true。
*/
public boolean getBit(int bit) {
return bitset.get(bit);
}
/**
* 設置過濾器某一位的值
*
* @param bit
* 要設置的位置.
* @param value
* true表示已經成功設置。false表示改爲被清除。
*/
public void setBit(int bit, boolean value) {
bitset.set(bit, value);
}
/**
* 返回存放信息的位數組.
*
* @return 位數組.
*/
public BitSet getBitSet() {
return bitset;
}
/**
* 得到過濾器中位數組個大小。
*
* @return 數組大小.
*/
public int size() {
return this.bitSetSize;
}
/**
* 返回已添加的元素的個數
*
* @return 元素個數.
*/
public int count() {
return this.numberOfAddedElements;
}
/**
* 得到能添加的元素的最大數量
*
* @return 最大數量.
*/
public int getExpectedNumberOfElements() {
return expectedNumberOfFilterElements;
}
/**
* 得到每個元素佔用的位的個數的期望值
*
* @return 每個元素佔用的位數
*/
public double getExpectedBitsPerElement() {
return this.bitsPerElement;
}
/**
* 得到每個元素佔用位數的實際值
*
* @return 每個元素佔用的位數.
*/
public double getBitsPerElement() {
return this.bitSetSize / (double) numberOfAddedElements;
}
}