实际上,布隆过滤器是一种数据结构(我的博客以"算法"开头的,都是指数据结构与算法)。布隆过滤器(BloomFilter)是一种比较巧妙的概率型数据结构,可以比较高效地进行插入和查询(本博客的实现暂不包含删除)操作。比如插入一些字符串后,可以查询某个字符串一定不存在,或者可能存在。一般用于URL查重、邮箱黑名单过滤等操作
本博客我打算实现的布隆过滤器的操作,有add()和exists()操作两种:
- add()操作,是往里面加入数据,本例是插入字符串。这些插入的字符串,将作为之后exists()操作的数据基础,也就是查询某个字符串在已经add()了的字符串集合中是否存在
- exists()操作,是查询某个数据是否存在,本例是查询字符串是否存在。由于布隆过滤器的算法原理,exists()操作有一定的误判率,可能会把不存在的字符串误判为存在。但是如果exists()操作判断不存在,那就一定不存在
布隆过滤器的原理:
- 创建一个有一定长度的bitmap,比如本例中的1021,用HashMap替代,下标就是0-1020
- 创建多个哈希函数,add()操作的时候分别对数据求哈希值,然后映射到bitmap中,然后映射上的bitmap从0变成1,也就是本例中的HashMap从false变成true
- 本例创建了五个哈希函数,分别对输入的字符串进行五种哈希:原哈希值、左右两半边子串哈希值,奇偶位字符提取的子串的哈希值,然后全部取绝对值,对1021取模,则所有哈希值都会映射到0-1020上面,然后映射到的HashMap的key就变为true
- exists()操作的时候,也是对输入的字符串求这五个哈希值,然后取绝对值,对1021取模。然后将这所有的结果映射值拿去检验,如果都是1,则可能存在,返回true。如果有一个不是1,则不可能存在,返回false
- 如果想降低误判率,可以将发现返回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