布隆过滤器
布隆过滤器是可以用于判断一个元素是不是在一个集合里,并且相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。
优点:占用空间小,查询快
缺点:有误判,删除困难
具体原理:链接
简单易懂的描述:
布隆过滤器。其实现方法就是:利用内存中一个长度为M的位数组B并初始化里面的所有位都为0,如下面的表格所示:
0 0 0 0 0 0 0 0 0 0
然后我们根据H个不同的散列函数,对传进来的字符串进行散列,并且每次的散列结果都不能大于位数组的长度。布隆过滤器的误判率取决于你使用多少个不同的散列函数。现在我们先假定有4个不同散列函数,传入一个字符串并进行一次插入操作,这时会进行4次散列,假设到了4个不同的下标,这个时候我们就会去数组中,将这些下标的位置置为1,数组变更为:
0 1 0 1 1 0 0 0 0 1
如果接下来我们再传入同一个字符串时,因为4次的散列结果都是跟上一次一样的,所以会得出跟上面一样的结果,所有应该置1的位都已经置1了,这个时候我们就可以认为这个字符串是已经存在的了。因此不难发现,这是会存在一定的误判率的,具体由你采用的散列函数质量,以及散列函数的数量确定。
最最原始的Java实现:
只做过简单测试(嗯。。。应该没问题吧)
import java.util.BitSet;
/**
* 最最原始的布隆过滤器类
*/
public class SimpleBloomFilter
{
// 设置布隆过滤器的大小
private static final int DEFAULT_SIZE = 2 << 24;
// 产生随机数的种子,可产生6个不同的随机数产生器。。。而且最好取素数
private static final int[] seeds = new int[]{7, 11, 13, 31, 37, 61};
// Java中的按位存储的思想,其算法的具体实现(布隆过滤器)
private BitSet bits = new BitSet(DEFAULT_SIZE);
// 根据随机数的种子,创建6个哈希函数
private SimpleHash[] func = new SimpleHash[seeds.length];
// 设置布隆过滤器所对应k(6)个哈希函数
public SimpleBloomFilter()
{
for (int i = 0; i < seeds.length; i++)
{
func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
}
}
public static void main(String[] args)
{
SimpleBloomFilter filter = new SimpleBloomFilter();
filter.add("123");
System.out.println(filter.contains("123"));
System.out.println(filter.contains("124"));
filter.add("124");
System.out.println(filter.contains("124"));
}
/**
* 添加
* @param str 值
*/
public void add(String str)
{
// 集齐6个hash值,准备(召唤神龙)添加
for (SimpleHash f:func)
{
bits.set(f.hash(str));
}
}
/**
* 判断是否存在
* @param str
* @return
*/
public boolean contains(String str)
{
// 根据此URL得到在布隆过滤器中的对应位,并判断其标志位(6个不同的哈希函数产生6种不同的映射)
for (SimpleHash f : func)
{
//当存在六位不都为0时,返回false
if (!bits.get(f.hash(str)))
{
return false;
}
}
return true;
}
/**
* 哈希类
*/
public static class SimpleHash
{
private int cap;
private int seed;
// 默认构造器,哈希表长默认为DEFAULT_SIZE大小,此哈希函数的种子为seed
public SimpleHash(int cap, int seed)
{
this.cap = cap;
this.seed = seed;
}
public int hash(String value)
{
int result = 0;
int len = value.length();
for (int i = 0; i < len; i++)
{
// 散列函数...重点,将此URL(使用到了集合中的每一个元素)散列到一个值
result = seed * result + value.charAt(i);
}
// 产生单个信息指纹。。。不能直接返回result,可能会越界
return (cap - 1) & result;
}
}
}
思考:
1.虽然布隆过滤器的数学原理好像很复杂的样子,但是真正实现起来并不是特别困难。。。。只是数学证明与描述。。。。。真的有点难
2.有所得,必有所失
就像基于比较的排序算法的时间复杂度下限是O(nLgn)一样,满足100%正确率,时间复杂度为O(1)的去重算法的空间复杂度下限应该是很接近于简单的hash算法了(没找到论文,不负责任的猜测一下)
而想要进一步降低空间复杂度,可能就需要放宽去重条件的限制了,比如去掉时间复杂度为O(1),那么做到空间复杂度为O(1)都是很简单的事。
而布隆过滤器就相当于去掉了100%正确率的条件限制,换取了空间复杂度的进一步缩小。