算法:用Java實現一個帶刪除功能的布隆過濾器(BloomFilter)

上一篇博客,我實現了一個迷你的布隆過濾器(BloomFilter)。今天我再實現一個帶刪除功能的布隆過濾器增強版

布隆過濾器刪除功能的原理,就是原來是通過true和false來判斷,而現在是通過計數來判斷了。add()操作就是往那些哈希值上面+1,刪除就是對應的哈希值-1。當然刪除之前需要判斷,如果有的哈希值都是0了,肯定就不讓刪了。和exists()操作一樣,刪除功能也有可能失效,就是刪除了之後還exists()還能返回true,因爲原來是通過true來判斷,現在是值大於零來判斷了。如果add()的太多,基數太大,則有可能delete()失效。使用的時候需要考慮這個問題(筆者也不太建議使用刪除功能)

接下來是我用Java實現的一個帶刪除功能的布隆過濾器,同樣自定義了五個字符串的哈希函數,用HashMap實現CountMap。相比於mini布隆過濾器,新增了delete()操作,返回是否刪除成功的boolean。其中算法的原理和精髓都在代碼和其間的詳細註釋中:

import java.util.HashMap;
import java.util.Map;

/**
 * @author LiYang
 * @ClassName BloomFilter
 * @Description 布隆過濾器的Java實現類,帶刪除功能
 * @date 2019/12/2 15:14
 */
public class BloomFilterEnhance {
    
    //布隆過濾器的哈希表的大小
    private static final int BLOOM_HASH_TABLE_SIZE = 1021;

    /**
     * 布隆過濾器自定義哈希函數1:
     * 返回字符串原始的哈希值絕對值
     * @param element 輸入字符串
     * @return 字符串原始的哈希值絕對值
     */
    private static int hashCodeAll(String element) {
        return Math.abs(element.hashCode());
    }

    /**
     * 布隆過濾器自定義哈希函數2:
     * 返回字符串左半邊的子串哈希值絕對值
     * @param element 輸入字符串
     * @return 字符串左半邊的子串哈希值絕對值
     */
    private static int hashCodeLeft(String element) {
        return Math.abs(element.substring(0, element.length() / 2).hashCode());
    }

    /**
     * 布隆過濾器自定義哈希函數3:
     * 返回字符串右半邊的子串哈希值絕對值
     * @param element 輸入字符串
     * @return 字符串右半邊的子串哈希值絕對值
     */
    private static int hashCodeRight(String element) {
        return Math.abs(element.substring(element.length() / 2).hashCode());
    }

    /**
     * 布隆過濾器自定義哈希函數4:
     * 返回字符串奇數位數字符組成的字符串的哈希值絕對值
     * @param element 輸入字符串
     * @return 字符串奇數位數字符組成的字符串的哈希值絕對值
     */
    private static int hashCodeOdd(String element) {
        StringBuffer sbuf = new StringBuffer();
        
        //收集奇數位數的字符
        for (int i = 0; i < element.length(); i++) {
            if ((i + 1) % 2 == 1) {
                sbuf.append(element.charAt(i));
            }
        }
        
        return Math.abs(sbuf.toString().hashCode());
    }

    /**
     * 布隆過濾器自定義哈希函數5:
     * 返回字符串偶數位數字符組成的字符串的哈希值絕對值
     * @param element 輸入字符串
     * @return 字符串偶數位數字符組成的字符串的哈希值絕對值
     */
    private static int hashCodeEven(String element) {
        StringBuffer sbuf = new StringBuffer();

        //收集偶數位數的字符
        for (int i = 0; i < element.length(); i++) {
            if ((i + 1) % 2 == 0) {
                sbuf.append(element.charAt(i));
            }
        }

        return Math.abs(sbuf.toString().hashCode());
    }
    
    
    //布隆過濾器的哈希表(模仿CountMap)
    private Map<Integer, Integer> bloomFilterCountMap = new HashMap<>();

    /**
     * 布隆過濾器的構造方法
     */
    public BloomFilterEnhance() {
        //初始化布隆過濾器"CountMap"
        for (int i = 0; i < BLOOM_HASH_TABLE_SIZE; i++) {
            bloomFilterCountMap.put(i, 0);
        }
    }

    /**
     * 布隆過濾器:新增操作
     * @param element 新增的字符串
     */
    public void add(String element) {
        //計算五種哈希函數得到的哈希值
        int bloomHashCodeAll = hashCodeAll(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeLeft = hashCodeLeft(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeRight = hashCodeRight(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeOdd = hashCodeOdd(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeEven =hashCodeEven(element) % BLOOM_HASH_TABLE_SIZE;
        
        //加入到布隆過濾器"CountMap"之中
        bloomFilterCountMap.put(bloomHashCodeAll, bloomFilterCountMap.get(bloomHashCodeAll) + 1);
        bloomFilterCountMap.put(bloomHashCodeLeft, bloomFilterCountMap.get(bloomHashCodeLeft) + 1);
        bloomFilterCountMap.put(bloomHashCodeRight, bloomFilterCountMap.get(bloomHashCodeRight) + 1);
        bloomFilterCountMap.put(bloomHashCodeOdd, bloomFilterCountMap.get(bloomHashCodeOdd) + 1);
        bloomFilterCountMap.put(bloomHashCodeEven, bloomFilterCountMap.get(bloomHashCodeEven) + 1);
    }

    /**
     * 布隆過濾器:查詢是否存在(有一定的誤判率,跟哈希表大小和哈希函數有關)
     * @param element 查詢存在與否的字符串
     * @return 該字符串是否存在
     */
    public boolean exists(String element) {
        //計算五種哈希函數得到的哈希值
        int bloomHashCodeAll = hashCodeAll(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeLeft = hashCodeLeft(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeRight = hashCodeRight(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeOdd = hashCodeOdd(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeEven =hashCodeEven(element) % BLOOM_HASH_TABLE_SIZE;

        //到布隆過濾器"CountMap"之中驗證
        //全部哈希值對應CountMap的值都大於零,則有可能存在
        //如果有一個不大於零,則肯定不存在
        return bloomFilterCountMap.get(bloomHashCodeAll) > 0
                && bloomFilterCountMap.get(bloomHashCodeLeft) > 0
                && bloomFilterCountMap.get(bloomHashCodeRight) > 0
                && bloomFilterCountMap.get(bloomHashCodeOdd) > 0
                && bloomFilterCountMap.get(bloomHashCodeEven) > 0;
    }

    /**
     * 布隆過濾器:刪除某個元素(必須是原先add()過的元素,否則有一定風險)
     * @param element 需要刪除的字符串
     * @return 是否刪除成功
     */
    public boolean delete(String element) {
        //如果需要刪除的element不存在,返回false
        if (!exists(element)) {
            return false;
        }
        
        //計算五種哈希函數得到的哈希值
        int bloomHashCodeAll = hashCodeAll(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeLeft = hashCodeLeft(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeRight = hashCodeRight(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeOdd = hashCodeOdd(element) % BLOOM_HASH_TABLE_SIZE;
        int bloomHashCodeEven =hashCodeEven(element) % BLOOM_HASH_TABLE_SIZE;

        //在布隆過濾器"CountMap"之中刪除該元素
        bloomFilterCountMap.put(bloomHashCodeAll, bloomFilterCountMap.get(bloomHashCodeAll) - 1);
        bloomFilterCountMap.put(bloomHashCodeLeft, bloomFilterCountMap.get(bloomHashCodeLeft) - 1);
        bloomFilterCountMap.put(bloomHashCodeRight, bloomFilterCountMap.get(bloomHashCodeRight) - 1);
        bloomFilterCountMap.put(bloomHashCodeOdd, bloomFilterCountMap.get(bloomHashCodeOdd) - 1);
        bloomFilterCountMap.put(bloomHashCodeEven, bloomFilterCountMap.get(bloomHashCodeEven) - 1);
        
        //刪除成功
        return true;
    }

    /**
     * 運行驗證布隆過濾器
     * @param args
     */
    public static void main(String[] args) {
        //布隆過濾器類
        BloomFilterEnhance bloomFilterEnhance = new BloomFilterEnhance();
        
        //先加入一週七天的英文字符
        bloomFilterEnhance.add("Monday");
        bloomFilterEnhance.add("Tuesday");
        bloomFilterEnhance.add("Wednesday");
        bloomFilterEnhance.add("Thursday");
        bloomFilterEnhance.add("Friday");
        bloomFilterEnhance.add("Saturday");
        bloomFilterEnhance.add("Sunday");

        //運行布隆過濾器,檢查結果
        System.out.println("布隆過濾器生效:");
        System.out.println("is Friday exists? " + bloomFilterEnhance.exists("Friday"));
        System.out.println("is FireChicken exists? " + bloomFilterEnhance.exists("FireChicken"));
        System.out.println();

        System.out.println("布隆過濾器生效:");
        System.out.println("is Saturday exists? " + bloomFilterEnhance.exists("Saturday"));
        System.out.println("is Sasurday exists? " + bloomFilterEnhance.exists("Sasurday"));
        System.out.println();

        //布隆過濾器誤刪
        boolean flag = bloomFilterEnhance.delete("Week");
        System.out.println("刪除Week(本身不存在),刪除是否成功:" + flag);
        System.out.println();

        System.out.println("布隆過濾器真刪除:成功");
        System.out.println("刪除前,Wednesday是否還在:" + bloomFilterEnhance.exists("Wednesday"));
        System.out.println("刪除Wednesday,是否成功:" + bloomFilterEnhance.delete("Wednesday"));
        System.out.println("刪除後,Wednesday是否還在:" + bloomFilterEnhance.exists("Wednesday"));
        System.out.println();

        System.out.println("布隆過濾器真刪除:成功");
        System.out.println("刪除前,Tuesday是否還在:" + bloomFilterEnhance.exists("Tuesday"));
        System.out.println("刪除Tuesday,是否成功:" + bloomFilterEnhance.delete("Tuesday"));
        System.out.println("刪除後,Tuesday是否還在:" + bloomFilterEnhance.exists("Tuesday"));
    }
    
}

運行BloomFilterEnhance類的main方法,增強布隆過濾器的數據結構和算法測試通過!由於刪除功能可能會入參之前未加入的字符串,造成數據變得混亂!所以個人建議還是不要使用刪除功能,還是用之前那個mini的布隆過濾器吧!

布隆過濾器生效:
is Friday exists? true
is FireChicken exists? false

布隆過濾器生效:
is Saturday exists? true
is Sasurday exists? false

刪除Week(本身不存在),刪除是否成功:false

布隆過濾器真刪除:成功
刪除前,Wednesday是否還在:true
刪除Wednesday,是否成功:true
刪除後,Wednesday是否還在:false

布隆過濾器真刪除:成功
刪除前,Tuesday是否還在:true
刪除Tuesday,是否成功:true
刪除後,Tuesday是否還在:false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章