实际上,布隆过滤器是一种数据结构(我的博客以"算法"开头的,都是指数据结构与算法)。布隆过滤器(BloomFilter)是一种比较巧妙的概率型数据结构,可以比较高效地进行插入和查询(本博客的实现暂不包含删除)操作。比如插入一些字符串后,可以查询某个字符串一定不存在,或者可能存在。一般用于URL查重、邮箱黑名单过滤等操作

本博客我打算实现的布隆过滤器的操作,有add()和exists()操作两种:

  1. add()操作,是往里面加入数据,本例是插入字符串。这些插入的字符串,将作为之后exists()操作的数据基础,也就是查询某个字符串在已经add()了的字符串集合中是否存在
  2. exists()操作,是查询某个数据是否存在,本例是查询字符串是否存在。由于布隆过滤器的算法原理,exists()操作有一定的误判率,可能会把不存在的字符串误判为存在。但是如果exists()操作判断不存在,那就一定不存在

布隆过滤器的原理:

  1. 创建一个有一定长度的bitmap,比如本例中的1021,用HashMap替代,下标就是0-1020
  2. 创建多个哈希函数,add()操作的时候分别对数据求哈希值,然后映射到bitmap中,然后映射上的bitmap从0变成1,也就是本例中的HashMap从false变成true
  3. 本例创建了五个哈希函数,分别对输入的字符串进行五种哈希:原哈希值、左右两半边子串哈希值,奇偶位字符提取的子串的哈希值,然后全部取绝对值,对1021取模,则所有哈希值都会映射到0-1020上面,然后映射到的HashMap的key就变为true
  4. exists()操作的时候,也是对输入的字符串求这五个哈希值,然后取绝对值,对1021取模。然后将这所有的结果映射值拿去检验,如果都是1,则可能存在,返回true。如果有一个不是1,则不可能存在,返回false
  5. 如果想降低误判率,可以将发现返回true但是不存在的数据做一个名单,进行二次判断,以减少误判率

接下来是我用Java实现的一个mini布隆过滤器,自定义了五个字符串的哈希函数,用HashMap替代BitMap。其中算法的原理和精髓都在代码和其间的详细注释中:

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

/**
 * @author LiYang
 * @ClassName BloomFilter
 * @Description 布隆过滤器的Java简单实现类,不带删除功能
 * @date 2019/12/2 15:14
 */
public class BloomFilter {
    
    //布隆过滤器的哈希表的大小
    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());
    }
    
    
    //布隆过滤器的哈希表(模仿BitMap)
    private Map<Integer, Boolean> bloomFilterBitMap = new HashMap<>();

    /**
     * 布隆过滤器的构造方法
     */
    public BloomFilter() {
        //初始化布隆过滤器"BitMap"
        for (int i = 0; i < BLOOM_HASH_TABLE_SIZE; i++) {
            bloomFilterBitMap.put(i, false);
        }
    }

    /**
     * 布隆过滤器:新增操作
     * @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;
        
        //加入到布隆过滤器"BitMap"之中
        bloomFilterBitMap.put(bloomHashCodeAll, true);
        bloomFilterBitMap.put(bloomHashCodeLeft, true);
        bloomFilterBitMap.put(bloomHashCodeRight, true);
        bloomFilterBitMap.put(bloomHashCodeOdd, true);
        bloomFilterBitMap.put(bloomHashCodeEven, true);
    }

    /**
     * 布隆过滤器:查询是否存在(有一定的误判率,跟哈希表大小和哈希函数有关)
     * @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;

        //到布隆过滤器"BitMap"之中验证
        //全部哈希值的BitMap对应值都为true,则有可能存在
        //如果有一个为false,则肯定不存在
        return bloomFilterBitMap.get(bloomHashCodeAll) 
                && bloomFilterBitMap.get(bloomHashCodeLeft)
                && bloomFilterBitMap.get(bloomHashCodeRight)
                && bloomFilterBitMap.get(bloomHashCodeOdd)
                && bloomFilterBitMap.get(bloomHashCodeEven);
    }

    /**
     * 运行验证布隆过滤器
     * @param args
     */
    public static void main(String[] args) {
        //布隆过滤器类
        BloomFilter bloomFilter = new BloomFilter();
        
        //先加入一周七天的英文字符
        bloomFilter.add("Monday");
        bloomFilter.add("Tuesday");
        bloomFilter.add("Wednesday");
        bloomFilter.add("Thursday");
        bloomFilter.add("Friday");
        bloomFilter.add("Saturday");
        bloomFilter.add("Sunday");

        //运行布隆过滤器,检查结果
        System.out.println("布隆过滤器生效:");
        System.out.println("is Sunday exists? " + bloomFilter.exists("Sunday"));
        System.out.println("is Moonday exists? " + bloomFilter.exists("Moonday"));
        System.out.println();

        System.out.println("布隆过滤器生效:");
        System.out.println("is Wednesday exists? " + bloomFilter.exists("Wednesday"));
        System.out.println("is wednesday exists? " + bloomFilter.exists("Windy"));
        System.out.println();

        System.out.println("布隆过滤器生效:");
        System.out.println("is Friday exists? " + bloomFilter.exists("Friday"));
        System.out.println("is FireChicken exists? " + bloomFilter.exists("FireChicken"));
        System.out.println();

        System.out.println("布隆过滤器生效:");
        System.out.println("is Saturday exists? " + bloomFilter.exists("Saturday"));
        System.out.println("is Sasurday exists? " + bloomFilter.exists("Sasurday"));
        System.out.println();

        System.out.println("布隆过滤器生效:");
        System.out.println("is Thursday exists? " + bloomFilter.exists("Thursday"));
        System.out.println("is Thu r sday exists? " + bloomFilter.exists("Thu r sday"));
    }
    
}

运行BloomFilter类的main方法,mini布隆过滤器的数据结构和算法测试通过!

布隆过滤器生效:
is Sunday exists? true
is Moonday exists? false

布隆过滤器生效:
is Wednesday exists? true
is wednesday exists? false

布隆过滤器生效:
is Friday exists? true
is FireChicken exists? false

布隆过滤器生效:
is Saturday exists? true
is Sasurday exists? false

布隆过滤器生效:
is Thursday exists? true
is Thu r sday exists? false