bloomfilter java_Java项目实战篇:用Redis快速实现BloomFilter!

背景

最近工作上有个类似需求是: 现有约3亿条数据词典存在于一个csv文件A中,作为数据源。对于 用户输入的任意单词M,需要快速的在A中匹配M单词是否存在。

(A文件约3G大小左右,总行数三亿)

拿到这个需求,你的第一想法怎么做呢?

正常思路可能是:

将csv文件A导入某关系型数据库。

sql查询按M匹配。

上面的方式有个明显的缺点是:慢!

3亿多行的数据,即便是建好索引进行检索,匹配到也得话不少时间(笔者没亲自试过,感兴趣的朋友可以自行测试测试,理论上快不起来的)。

目前能 在时间复杂度和空间复杂度上达到最佳的方案,恐怕就是Bloom Filter了, 维基地址:Bloom Filter

此处给不太了解Bloom Filter的读者看,熟悉的朋友直接看下一节。

本文场景Bloom Filter 使用思路解释:

假设申请了一段bit位大数组(即数组中的元素只能是一个bit位,1或0,默认元素值都为0)

将csv文件A中的每个单词,经过多个hash函数进行hash运算之后得到在大数组中对应的多个下标位置

将步骤2中得到的多个下标位置的bit位都置为1.

对于用户输入的任意单词M,按照2的步骤得到多个下标位置,其对应大数组中的值全部为1则存在,否则不存在。

方案选型

实现Bloom Filter的方法很多,有各种语言版本的,这里为了真切感受一下算法的魅力,笔者这里决定用java代码徒手撸了!

另一方面,考虑到分布式应用的需要,显然在单机内存上构建 Bloom Filter 存储是不太合适的。 这里选择 redis 。

redis有以下为操作,可以用于实现bloomfilter:

redis>SETBIT bit 10086 1(integer)0redis>GETBIT bit 10086(integer)1redis>GETBIT bit 100# bit 默认被初始化为 0(integer)0Click to copy

实现细节

实现bloom filter的关键是hash函数,一般为了降低误报率、减少hash碰撞的影响,会选择多个hash函数。

那么,怎么写一个hash函数呢?

不要方,我们要的hash是 input: String --> output: int , jdk里面的String类不是恰好也有一个hashCode 方法吗? 翻出来看一看!

/**

* Returns a hash code for this string. The hash code for a

* {@code String} object is computed as

*

 
  

* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

*

* using {@code int} arithmetic, where {@code s[i]} is the

* ith character of the string, {@code n} is the length of

* the string, and {@code ^} indicates exponentiation.

* (The hash value of the empty string is zero.)

*

* @return  a hash code value for this object.

*/publicinthashCode(){inth=hash;if(h==0&&value.length>0){charval[]=value;for(inti=0;i

看到这一行 h = 31 * h + val[i]; ,貌似原理其实也很简单,每个字符对应的ascii码,经过一个公式计算依次加起来。这里有个系数31 , 稍微变一下, 不就可以有多个hash函数了吗。

以下是稍加修改后的hash函数:

//总的bitmap大小  64Mprivatestaticfinalintcap=1<<29;/*

* 不同哈希函数的种子,一般取质数

* seeds数组共有8个值,则代表采用8种不同的哈希函数

*/privateint[]seeds=newint[]{3,5,7,11,13,31,37,61};privateinthash(String value,intseed){intresult=0;intlength=value.length();for(inti=0;i

剩下的事情便很简单了,对每个词典A中的单词,依次调seeds 中对应的hash函数(这里一共是8个),用redis的setbit操作,将下标值置为1.

redis代码 (这里用pipeline 包装了下。)

@ServicepublicclassRedisService{@AutowiredprivateStringRedisTemplate template;publicvoidmultiSetBit(String name,booleanvalue,long...offsets){template.executePipelined((RedisCallback)connection->{for(longoffset:offsets){connection.setBit(name.getBytes(),offset,value);}returnnull;});}publicListmultiGetBit(String name,long...offsets){Listresults=template.executePipelined((RedisCallback)connection->{for(longoffset:offsets){connection.getBit(name.getBytes(),offset);}returnnull;});Listlist=newArrayList<>();results.forEach(obj->{list.add((Boolean)obj);});returnlist;}}Click to copy

最后,代码串起来大概长这个样子:

FileInputStream inputStream=newFileInputStream("/XXXX.csv");BufferedReader bufferedReader=newBufferedReader(newInputStreamReader(inputStream));HashSettotalSet=newHashSet<>();String word=null;while((word=bufferedReader.readLine())!=null){for(intseed:seeds){inthash=hash(word,seed);totalSet.add((long)hash);}long[]offsets=newlong[totalSet.size()];inti=0;for(Long l:totalSet){offsets[i++]=l;}redisService.multiSetBit("BLOOM_FILTER_WORDS_DICTIONARY",true,offsets);}Click to copy

查的时候也类似:

String word="XXXX";//实际输入long[]offsets=newlong[seeds.length];for(inti=0;iresults=redisService.multiGetBit("BLOOM_FILTER_WORDS_DICTIONARY",offsets);//判断是否都为true (则存在)booleanisExisted=true;for(Boolean result:results){if(!result){isExisted=false;break;}}Click to copy

注意事项

setbit的offset是用大小限制的,在0到 232(最大使用512M内存)之间,即0~4294967296之前,超过这个数会自动将offset转化为0,因此使用的时候一定要注意。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值