哈希扩展——布隆过滤器
本文用到的位图操作,见博客:哈希扩展—位图的相关操作实现
首先,布隆过滤器是用于判断一个字符串元素是否在集合中存在。
我们想要判断一个元素是否在集合当中,一般的做法是先将元素保存起来,然后再比较确定集合中是否存在。我们之前介绍过的链表、二叉树等都是这种思路。但是随着集合中元素的增加,我们需要的存储空间越来越大,我们遍历比较确定的速度也越来越慢。但是我们介绍的哈希表可以实现随机访问,它通过利用一个哈希函数将一个元素映射到某一个点。这样我们只需要访问该点的状态就知道该元素是否存在于集合当中了。后来的位图也是这种思想,但是在哈希表之上更节省空间,利用一个比特位来表示是否存在。本文要介绍的布隆过滤器也是这样的实现思想。
但是最大的问题就是哈希冲突。我们要判断一个元素是否在集合中,如果只通过一个哈希函数映射的比特位来表示,很有可能我们会遇到多个元素对应同一个比特位的状况。所以为了解决这种问题,我们在实现布隆过滤器的时候,用多个哈希函数求得多个位置,将这多个位置的比特位都设置为1(表示存在)。在判断元素是否存在于集合中,我们要检测多个哈希函数对应的比特位是否都表示存在,若有一个表示不存在,就说明该元素不再集合中,只有求得的所有比特位都表示存在才说明该元素存在。
注意:
(1)布隆过滤器实现过程中,只能插入元素,不能删除。因为你不知道某个比特位的“1”只表示了某一个元素存在状态,还是对应了多个元素的存在。若是对应对个元素,你删某个元素时将该位置为“0”,此时其他的元素还存在,但是在我们判断时就因为该位为0认为其他不存在,这是错误的;
(2)虽然用了多个哈希函数来实现,但是还是有产生哈希冲突的可能,且随着元素越来越大这个可能性也会越大。
下面我们就实现布隆过滤器:
1. 结构定义
因为布隆过滤器基于位图实现,所以布隆过滤器要有一个位图,用于表示状态(是否存在,“1”表示存在,“0”表示不存在)
//实现布隆过滤器
//基于位图实现,表示字符串是否存在
#pragma once
#include <stdio.h>
#include <stdint.h>//uint64_t
#include <stdlib.h>
#include <string.h>
#define SHOW_NAME printf("\n========================%s=======================\n", __FUNCTION__);
#define BLOOMHASHCOUNT 2
typedef uint64_t BitmapType;//uint64_t类型可跨平台使用,占8字节
typedef uint64_t(* BloomHash)(const char*);//实现布隆过滤器的哈希函数
typedef struct Bitmap//位图
{
BitmapType* data;
uint64_t capacity;//能容纳的最多元素个数(单位是比特位)
}Bitmap;
typedef struct BloomFilter//布隆过滤器
{
Bitmap bm;//用于表示状态的位图
BloomHash bloom_hash[BLOOMHASHCOUNT];//存放多个哈希函数
}BloomFilter;
2. 初始化
这里初始化分两步:
(1)将位图初始化:位图的容量
(2)初始化实现布隆过滤器的哈希函数:我们这里用了两个哈希函数,但是为了减少哈希冲突的可能性,哈希函数当然多一点比较好,这个根据程序员自己决定
调用的哈希函数:
uint64_t BKDRHash(const char* str)
{
uint64_t hash = 0;
uint64_t ch;
while(ch = (uint64_t)*str++)
{
hash = 65599*hash + ch;
}
return hash;
}
uint64_t SDBMHash(const char* str)
{
uint64_t hash = 0;
uint64_t ch;
while(ch = (uint64_t)*str++)
{
hash = 131*hash + ch;
}
return hash;
}
初始化代码:
//1.初始化
void BloomFilterInit(BloomFilter* bf)
{
if(bf == NULL)//非法操作
return;
BitmapInit(&bf->bm, 1000);//该位图最多标记1000个数据
bf->bloom_hash[0] = SDBMHash;
bf->bloom_hash[1] = BKDRHash;
return;
}
3. 销毁
销毁与初始化类似,同样分两步:销毁位图,将调用的哈希函数置NULL
//2.销毁
void BloomFilterDestroy(BloomFilter* bf)
{
if(bf == NULL)
return;
bf->bloom_hash[0] = NULL;
bf->bloom_hash[1] = NULL;
BitmapDestroy(&bf->bm);
return;
}
4. 插入元素
(1)调用每个哈希函数,求出每个哈希函数对应的下标hash;
(2)将每个hash对应的比特位置为1,表示存在
//3.插入
//用多个哈希函数计算出不同位置的下标,在位图中将对应下标全部置为1,用来表示某个字符串的存在
void BloomFilterInsert(BloomFilter* bf, const char* str)
{
if(bf == NULL || str == NULL)
return;
size_t i = 0;
for(i=0; i<BLOOMHASHCOUNT; ++i)
{
uint64_t hash = bf->bloom_hash[i](str) % (bf->bm.capacity);
BitmapSet(&bf->bm, hash);
}
return;
}
5. 查找
要注意的就是,每个哈希函数对应下标位置的状态都表示存在,该字符串才存在,否则不存在。
//4.查找
//每个哈希函数对应的下标位置都表示存在,该字符串才存在
int BloomFilterFind(BloomFilter* bf, const char* str)
{
if(bf == NULL || str == NULL)
return 0;
size_t i = 0;
for(; i<BLOOMHASHCOUNT; ++i)
{
uint64_t hash = bf->bloom_hash[i](str) % (bf->bm.capacity);
int ret = BitmapTest(&bf->bm, hash);
if(ret == 0)
return 0;
}
return 1;
}
最后是测试代码:
int main()
{
BloomFilter bf;
BloomFilterInit(&bf);
printf("expcted is 1000, actual is %d\n", bf.bm.capacity);
printf("expcted is %p, actual is %p\n", SDBMHash, bf.bloom_hash[0]);
printf("expcted is %p, actual is %p\n", BKDRHash, bf.bloom_hash[1]);
BloomFilterInsert(&bf, "this is youngmay");
int ret = BloomFilterFind(&bf, "this is youngmay");
printf("expcted is 1, actual is %d\n", ret);
ret = BloomFilterFind(&bf, "Jony J YoungJack");
printf("expcted is 0, actual is %d\n", ret);
BloomFilterDestroy(&bf);
printf("expcted is 0, actual is %d\n", bf.bm.capacity);
printf("expcted is nil, actual is %p\n", bf.bloom_hash[0]);
printf("expcted is nil, actual is %p\n", bf.bloom_hash[1]);
return 0;
}