淺談BloomFilter【下】用Java實現BloomFilter

通過前一篇文章的學習,對於 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;
    }
}


發佈了86 篇原創文章 · 獲贊 65 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章