C#布隆過濾器的實現

原理

見:https://www.cnblogs.com/mushroom/p/4556801.html

源碼

點此下載

布隆過濾器作用

  1. 解決緩存穿透問題
  2. 過濾重複數據
  3. ...

在C#中的實現方式之一(來自外網)

    /// <summary>
    /// 布隆過濾器
    /// </summary>
    /// <typeparam name="T">數據類型</typeparam>
    public class Filter<T>
    {
        /// <summary>
        /// 一種可用於散列輸入的函數。 
        /// </summary>
        /// <param name="input">要計算Hash的值</param>
        /// <returns>生成的Hash值</returns>
        public delegate int HashFunction(T input);

        /// <summary>
        /// 根據所需的容量和錯誤率以及Hash函數的最佳數量,使用底層數據結構的最佳大小,創建一個誤判率爲 1/capacity的布隆過濾器。  
        /// 如果您的類型T是string或int,則將爲您提供內置的Hash函數。否則將引發異常。如果您沒有使用這些類型,請使用支持自定義Hash函數的重載。
        /// </summary>
        /// <param name="capacity">要添加到過濾器中的項目的預計數量。可以添加超過此數量的項目,但誤判率將超過預期。</param>
        public Filter(int capacity) : this(capacity, null) { }

        /// <summary>
        /// 根據所需的容量和錯誤率以及Hash函數的最佳數量,使用底層數據結構的最佳大小,創建一個誤判率爲 1/capacity的布隆過濾器。  
        /// 如果您的類型T是string或int,則將爲您提供內置的Hash函數。否則將引發異常。如果您沒有使用這些類型,請使用支持自定義Hash函數的重載。
        /// </summary>
        /// <param name="capacity">要添加到過濾器中的項目的預計數量。可以添加超過此數量的項目,但誤判率將超過預期。</param>
        /// <param name="errorRate">可接受的誤判率(例如, 0.01F = 1%)</param>
        public Filter(int capacity, float errorRate) : this(capacity, errorRate, null) { }

        /// <summary>
        /// 根據所需的容量和錯誤率以及Hash函數的最佳數量,使用底層數據結構的最佳大小,創建一個誤判率爲 1/capacity的布隆過濾器。  
        /// 如果您的類型T是string或int,則將爲您提供二級Hash函數。否則將引發異常。如果您沒有使用這些類型,請使用支持自定義Hash函數的重載。
        /// </summary>
        /// <param name="capacity">要添加到過濾器中的項目的預計數量。可以添加超過此數量的項目,但誤判率將超過預期。</param>
        /// <param name="hashFunction">對輸入值進行哈希運算的函數。請勿使用GetHashCode()。如果此參數爲null,且T爲string或int,則將爲您提供內置的Hash函數。</param>
        public Filter(int capacity, HashFunction hashFunction) : this(capacity, BestErrorRate(capacity), hashFunction) { }

        /// <summary>
        /// 根據所需的容量和錯誤率以及Hash函數的最佳數量,使用底層數據結構的最佳大小,創建一個誤判率爲 1/capacity的布隆過濾器。  
        /// 如果您的類型T是string或int,則將爲您提供二級Hash函數。否則將引發異常。如果您沒有使用這些類型,請使用支持自定義Hash函數的重載。
        /// </summary>
        /// <param name="capacity">要添加到過濾器中的項目的預計數量。可以添加超過此數量的項目,但誤判率將超過預期。</param>
        /// <param name="hashFunction">對輸入值進行哈希運算的函數。請勿使用GetHashCode()。如果此參數爲null,且T爲string或int,則將爲您提供內置的Hash函數。</param>
        /// <param name="errorRate">可接受的誤判率 (例如: 0.01F = 1%)</param>
        public Filter(int capacity, float errorRate, HashFunction hashFunction) : this(capacity, errorRate, hashFunction, BestM(capacity, errorRate), BestK(capacity, errorRate)) { }

        /// <summary>
        /// 創建一個新的布隆過濾器實例
        /// </summary>
        /// <param name="capacity">要添加到過濾器中的項目的預計數量。可以添加超過此數量的項目,但誤判率將超過預期。</param>
        /// <param name="errorRate">可接受的誤判率 (例如: 0.01F = 1%)</param>
        /// <param name="hashFunction">對輸入值進行哈希運算的函數。請勿使用GetHashCode()。如果此參數爲null,且T爲string或int,則將爲您提供內置的Hash函數。</param>
        /// <param name="m">BitArray的容量</param>
        /// <param name="k">使用的Hash函數數量</param>
        public Filter(int capacity, float errorRate, HashFunction hashFunction, int m, int k)
        {
            // 驗證參數是否在範圍內
            if (capacity < 1)
                throw new ArgumentOutOfRangeException(nameof(capacity), capacity, $@"{nameof(capacity)} 必須大於0");
            if (errorRate >= 1 || errorRate <= 0)
                throw new ArgumentOutOfRangeException(nameof(errorRate), errorRate,
                    $@"{nameof(errorRate)} 必須介於0和1之間");
            if (m < 1) // 說明 BestM 函數溢出了
                throw new ArgumentOutOfRangeException(
                    $"提供的{nameof(capacity)}和{nameof(errorRate)}導致了BitArray的長度超過了[{int.MaxValue}]。 請減少其中一個值。");

            // 設置次級Hash函數
            if (hashFunction == null)
            {
                if (typeof(T) == typeof(String))
                {
                    _getHashSecondary = HashString;
                }
                else if (typeof(T) == typeof(int))
                {
                    _getHashSecondary = HashInt32;
                }
                else
                {
                    throw new ArgumentNullException(nameof(HashFunction), @"當T不是字符串或int時,請爲您的類型T提供散列函數。");
                }
            }
            else
                _getHashSecondary = hashFunction;

            _hashFunctionCount = k;
            _hashBits = new BitArray(m);
        }

        /// <summary>
        /// 添加新的項到過濾器。該項無法被移除。
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            // 開始翻轉項目的每個散列的位 
            int primaryHash = item.GetHashCode();
            int secondaryHash = _getHashSecondary(item);
            for (int i = 0; i < _hashFunctionCount; i++)
            {
                int hash = ComputeHash(primaryHash, secondaryHash, i);
                _hashBits[hash] = true;
            }
        }

        /// <summary>
        ///  檢查給定概率的篩選器中項目是否存在。 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(T item)
        {
            int primaryHash = item.GetHashCode();
            int secondaryHash = _getHashSecondary(item);
            for (int i = 0; i < _hashFunctionCount; i++)
            {
                int hash = ComputeHash(primaryHash, secondaryHash, i);
                if (_hashBits[hash] == false)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// 過濾器中false位與true位的比率。例如,容量爲10的過濾器中只有1位true,那麼意味着真實度爲0.1。
        /// </summary>
        public double Truthiness => (double)TrueBits() / _hashBits.Count;

        private int TrueBits()
        {
            int output = 0;
            foreach (bool bit in _hashBits)
            {
                if (bit == true)
                    output++;
            }
            return output;
        }

        /// <summary>
        /// 執行 Dillinger & Manolio 雙散列(使用Dillinger 、 Manolio兩位的研究結果)
        /// </summary>
        /// <param name="primaryHash">主Hash值</param>
        /// <param name="secondaryHash">次級Hash值</param>
        /// <param name="i">Hash函數個數索引</param>
        /// <returns></returns>
        private int ComputeHash(int primaryHash, int secondaryHash, int i)
        {
            int resultingHash = (primaryHash + (i * secondaryHash)) % _hashBits.Count;
            return Math.Abs((int)resultingHash);
        }

        private readonly int _hashFunctionCount;
        private readonly BitArray _hashBits;
        private readonly HashFunction _getHashSecondary;

        /// <summary>
        /// 計算最佳K值(代表使用的Hash函數數量)
        /// </summary>
        /// <param name="capacity">預計添加到過濾器中的項目數量</param>
        /// <param name="errorRate">錯誤率</param>
        /// <returns></returns>
        private static int BestK(int capacity, float errorRate)
        {
            return (int)Math.Round(Math.Log(2.0) * BestM(capacity, errorRate) / capacity);
        }

        /// <summary>
        /// 計算最佳M值(BitArray容量)
        /// </summary> 
        /// <param name="capacity">預計添加到過濾器中的項目數量</param>
        /// <param name="errorRate">錯誤率</param>
        /// <returns></returns>
        private static int BestM(int capacity, float errorRate)
        {
            return (int)Math.Ceiling(capacity * Math.Log(errorRate, (1.0 / Math.Pow(2, Math.Log(2.0)))));
        }

        /// <summary>
        /// 最佳誤判率
        /// </summary>
        /// <param name="capacity"></param>
        /// <returns></returns>
        private static float BestErrorRate(int capacity)
        {
            float c = (float)(1.0 / capacity);
            if (c != 0)
                return c;
            else
                return (float)Math.Pow(0.6185, int.MaxValue / capacity); // 參考:http://www.cs.princeton.edu/courses/archive/spring02/cs493/lec7.pdf
        }

        /// <summary> 
        /// 使用Thomas Wang的方法v3.1對32位帶符號int進行散列 (http://www.concentric.net/~Ttwang/tech/inthash.htm).
        /// 運行時建議11個週期
        /// </summary>
        /// <param name="input">將被散列的整數</param>
        /// <returns>散列結果</returns>
        private static int HashInt32(T input)
        {
            uint? x = input as uint?;
            unchecked
            {
                x = ~x + (x << 15); // x = (x << 15) - x- 1, as (~x) + y 相當於 y - x - 1 在二進制中的補碼錶示
                x = x ^ (x >> 12);
                x = x + (x << 2);
                x = x ^ (x >> 4);
                x = x * 2057; // x = (x + (x << 3)) + (x<< 11);
                x = x ^ (x >> 16);
                return (int)x;
            }
        }

        /// <summary>
        ///  使用來自Dr. Dobbs Journal雜誌中Jenkin的 "One At A Time" 方法散列一個字符串成 (雜誌地址:http://burtleburtle.net/bob/hash/doobs.html).
/// Jenkin hash function WIKI:(https://en.wikipedia.org/wiki/Jenkins_hash_function/// 運行時建議爲9x+9,其中x = input.Length。 /// </summary> /// <param name="input">要散列的字符串</param> /// <returns>散列結果</returns> private static int HashString(T input) { string s = input as string; int hash = 0; if (s != null) { foreach (var t in s) { hash += t; hash += (hash << 10); hash ^= (hash >> 6); } } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } }

 

 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章