BloomFiler又叫布隆过滤器,是一种思路,一种数据结构,特色是高效地插入和查询,能够用来告诉你 “某样东西必定不存在或者可能存在”。相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,可是缺点是其返回的结果是几率性的,而不是确切的。redis
使用场景:数组
须要判断某个值是否存在,但存储量较大,存储空间浪费是个问题;且常规的比较查询效率较低。数据结构
缺点:函数
随着数据的增长,误判率随之增长,只能判断数据是否必定不存在,而没法判断数据是否必定存在;没法作到删除数据。性能
实现原理:搜索引擎
好比你有1个Url,你彻底能够建立一长度是100bit的数组,而后对url分别用5个不一样的hash函数进行hash,获得5个hash后的值,这5个值尽量的保证均匀分布在100个bit的范围内。而后把5个hash值对应的bit位都置为1,判断一个url是否已经存在时,一次看5个bit位是否为1就能够了,若是有任何一个不为1,那么说明这个url不存在。这里须要注意的是,若是对应的bit位值都为1,那么也不能确定这个url必定存在。url
BloomFilter的核心思想有两点:spa
尽管BloomFilter已经尽量的减少hash碰撞的几率了,可是,并不能完全消除,所以正如上面提到的:
若是对应的bit位值都为1,那么也不能确定这个url必定存在
也就是说,BloomFilter实际上是存在必定的误判的,这个误判的几率显然和数组的大小以及hash函数的个数以及每一个hash函数自己的好坏有关。设计
redis实现布隆过滤器code
Redis 因其支持 setbit 和 getbit 操做,且纯内存性能高等特色,所以自然就能够做为布隆过滤器来使用。可是布隆过滤器的不当使用极易产生大 Value,增长 Redis 阻塞风险,所以生成环境中建议对体积庞大的布隆过滤器进行拆分。
用 redis来实现布隆过滤器,咱们要使用的数据结构是bitmap,你可能会有疑问,redis支持五种数据结构:String,List,Hash,Set,ZSet,没有bitmap呀。没错,实际上bitmap的本质仍是String。
要用redis来实现布隆过滤器,咱们须要本身设计映射函数,本身度量二进制向量的长度,这对我来讲,无疑是一个不可能完成的任务,只能借助搜索引擎,下面直接放出代码:
public class RedisMain { static final int expectedInsertions = 100;//要插入多少数据 static final double fpp = 0.01;//指望的误判率 //bit数组长度 private static long numBits; //hash函数数量 private static int numHashFunctions; static { numBits = optimalNumOfBits(expectedInsertions, fpp); numHashFunctions = optimalNumOfHashFunctions(expectedInsertions, numBits); } public static void main(String[] args) { Jedis jedis = new Jedis("192.168.0.109", 6379); for (int i = 0; i < 100; i++) { long[] indexs = getIndexs(String.valueOf(i)); for (long index : indexs) { jedis.setbit("codebear:bloom", index, true); } } for (int i = 0; i < 100; i++) { long[] indexs = getIndexs(String.valueOf(i)); for (long index : indexs) { Boolean isContain = jedis.getbit("codebear:bloom", index); if (!isContain) { System.out.println(i + "确定没有重复"); } } System.out.println(i + "可能重复"); } } /** * 根据key获取bitmap下标 */ private static long[] getIndexs(String key) { long hash1 = hash(key); long hash2 = hash1 >>> 16; long[] result = new long[numHashFunctions]; for (int i = 0; i < numHashFunctions; i++) { long combinedHash = hash1 + i * hash2; if (combinedHash < 0) { combinedHash = ~combinedHash; } result[i] = combinedHash % numBits; } return result; } private static long hash(String key) { Charset charset = Charset.forName("UTF-8"); return Hashing.murmur3_128().hashObject(key, Funnels.stringFunnel(charset)).asLong(); } //计算hash函数个数 private static int optimalNumOfHashFunctions(long n, long m) { return Math.max(1, (int) Math.round((double) m / n * Math.log(2))); } //计算bit数组长度 private static long optimalNumOfBits(long n, double p) { if (p == 0) { p = Double.MIN_VALUE; } return (long) (-n * Math.log(p) / (Math.log(2) * Math.log(2))); } }
如何删除
目前咱们知道布隆过滤器能够支持 add 和 isExist 操做,那么 delete 操做能够么,答案是不能够,例如一个 bit 位 被两个值共同覆盖的话,一旦你删除其中一个值而将其bit 位 置 0,那么下次判断另外一个值是否存在的话,会直接返回 false,而实际上你并无删除另外一个值。
如何解决这个问题,答案是计数删除。可是计数删除须要存储一个数值,而不是原先的 bit 位,会增大占用的内存大小。这样的话,增长一个值就是将对应索引槽上存储的值加一,删除则是减一,判断是否存在则是看值是否大于0。
该实现叫作 counting BloomFiler。
若是但愿百分百准确,BloomFilter只适合作一层数据过滤(过滤掉必定不存在的),并不能彻底解决你的问题,还须要其余的解决方案。