Guava 布隆过滤器
在 Guava 项目的11.0版中,一个新的类添加了进来—— BloomFilter(布隆过滤器)类。布隆过滤器是一种独特的数据结构,用以表明元素是否被保存在一个集合(Set)中。有趣的是,布隆过滤器能够明确指出元素 绝对不存在于一个集合中,或是 可能存在于一个集合中。由于布隆过滤器从不漏报的特性,使得它成为避免不必要和昂贵操作的约束条件的极好选择。 近来对布隆过滤器的关注开始增多,如要使用它,你可以自己写代码,也可以谷歌之。编写自己的布隆过滤器的问题在于使用正确的哈希函数来确保过滤器生效。鉴于 Guava 是使用 Murmur Hash 来实现的,仅需一个库,你就能获得这个非常有效的布隆过滤器。
http://codingjunkie.net/guava-bloomfilter/
布隆过滤器速成
布隆过滤器在本质上是二进制向量。在高层级上,布隆过滤器以下面的方式工作:
添加元素到过滤器。
对元素进行几次哈希运算,当索引匹配哈希的结果时,将该位设置为 1 的。
如果要检测元素是否属于集合,使用相同的哈希运算步骤,检查相应位的值是1还是0。这就是布隆过滤器明确元素不存在的过程。如果位未被设置,则元素绝不可能存在于集合中。当然,一个肯定的答案意味着,要不就是元素存在于集合中,要不就是遇见了哈希冲突。这里有一份很好的对布隆过滤器细节的描述,还有一份很好的教程。依据维基百科,谷歌在 BigTable 中使用了布隆过滤器,以避免在硬盘中寻找不存在的条目。另一个有趣的用法是使用布隆过滤器优化SQL查询。
使用 Guava 的布隆过滤器
Guava 的布隆过滤器通过调用 BloomFilter 类中的静态函数创建, 传递一个 Funnel 对象以及一个代表预期插入数量整数。同样来自于 Guava 11 中的 Funnel 对象,用于将数据发送给一个接收器(Sink)。 下面的例子是一个默认的实现,有着3%的误报率。Guava 提供的 Funnels 类拥有两个静态方法提供了将CharSequence 或byte数组插入到过滤器的 Funnel 接口的实现。
//Creating the BloomFilterBloomFilter bloomFilter = BloomFilter.create(Funnels.byteArrayFunnel(), 1000);
//Putting elements into the filter//A BigInteger representing a key of some sortbloomFilter.put(bigInteger.toByteArray());
//Testing for element in setboolean mayBeContained = bloomFilter.mayContain(bitIntegerII.toByteArray());
更新:根据来自 Louis Wasserman 的回复,下面是如何为 BigIntegers 创建一个带有自定义 Funnel 实现的布隆过滤器:
//Create the custom filterclass BigIntegerFunnel implements Funnel<BigInteger> {@Overridepublic void funnel(BigInteger from, Sink into) { into.putBytes(from.toByteArray()); } }
//Creating the BloomFilterBloomFilter bloomFilter = BloomFilter.create(new BigIntegerFunnel(), 1000);
//Putting elements into the filter//A BigInteger representing a key of some sortbloomFilter.put(bigInteger);
//Testing for element in setboolean mayBeContained = bloomFilter.mayContain(bitIntegerII);
注意事项
正确估计预期插入数量是非常关键的。当插入的数量接近或高于预期值的时候,布隆过滤器将会填满,这样的话,它会产生很多无用的误报点。这里有另一个版本的 BloomFilter.create 方法,它额外接收一个参数,一个代表假命中概率水平的双精度数字(必须大于零且小于1)。假命中概率等级影响哈希表储存或搜索元素的数量。百分比越低,哈希表的性能越好。
BloomFilterTest
package bbejeck.guava.hash;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;
import org.junit.Before;
import org.junit.Test;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Created by IntelliJ IDEA.
* User: bbejeck
*/
public class BloomFilterTest {
private BloomFilter<BigInteger> bloomFilter;
private Random random;
private int numBits;
private List<BigInteger> stored;
private List<BigInteger> notStored;
@Before
public void setUp() {
numBits = 128;
random = new Random();
stored = new ArrayList<BigInteger>();
notStored = new ArrayList<BigInteger>();
loadBigIntList(stored, 1000);
loadBigIntList(notStored, 100);
}
@Test
public void testMayContain() {
setUpBloomFilter(stored.size());
int falsePositiveCount = 0;
for (BigInteger bigInteger : notStored) {
boolean mightContain = bloomFilter.mightContain(bigInteger);
boolean isStored = stored.contains(bigInteger);
if (mightContain && !isStored) {
falsePositiveCount++;
}
}
assertThat(falsePositiveCount < 5, is(true));
}
@Test
public void testMayContainGoOverInsertions() {
setUpBloomFilter(50);
int falsePositiveCount = 0;
for (BigInteger bigInteger : notStored) {
boolean mightContain = bloomFilter.mightContain(bigInteger);
boolean isStored = stored.contains(bigInteger);
if (mightContain && !isStored) {
falsePositiveCount++;
}
}
assertThat(falsePositiveCount, is(notStored.size()));
}
private void setUpBloomFilter(int numInsertions) {
bloomFilter = BloomFilter.create(new BigIntegerFunnel(), numInsertions);
addStoredBigIntegersToBloomFilter();
}
private BigInteger getRandomBigInteger() {
return new BigInteger(numBits, random);
}
private void addStoredBigIntegersToBloomFilter() {
for (BigInteger bigInteger : stored) {
bloomFilter.put(bigInteger);
}
}
private void loadBigIntList(List<BigInteger> list, int count) {
for (int i = 0; i < count; i++) {
list.add(getRandomBigInteger());
}
}
private class BigIntegerFunnel implements Funnel<BigInteger> {
@Override
public void funnel(BigInteger from, PrimitiveSink into) {
into.putBytes(from.toByteArray());
}
}
}
背景
原有的去重方案是:
使用linux命令去重
缺点
优点
实现简单
一般功能稳定
出现问题只能重来,控制粒度很粗。
程序与操作系统过渡耦合,如果系统中sort或者uniq命令出现问题,则去重功能不能使用。
使得push opt的用户数据以文件的形式存在,不方便多主机、操作系统共享,妨碍后期push opt多主节点发展。
使用tair去重
缺点
优点
一般不会出现问题;
访问迅速;
接近
O(1)
时间复杂度得知哪条重复。适合分布式系统中去重
浪费大量珍贵的内存资源
不一定可靠,也可能丢失数据
tair出现问题后,用户去重功能彻底不能使用。
为什么使用布隆过滤器
原理

初始化:对于x,y,z三个数,经过{k1,k2,k3}三个hash函数,将向量空间中的某些位置标记为1,作为初始向量空间。
判断:当新进入一个数据,w,进过{k1,k2,k3}hash函数,在向量空间上所有位置均为1,表示命中这个数,这个数已经在bloomfilter中。如果部分为1或者全为0,表示这个数不在bloomfilter中。
性能分析
参考:https://blog.csdn.net/jiaomeng/article/details/1495500
如果hash函数个数为k个,那么bloom过滤器插入和判断一个数的时间复杂度是O(k)
,空间复杂度为O(size)
。size为bloomfilter的位数组大小。
优缺点分析
优点:常数时间复杂度,占用很少的内存。
缺点:不会漏判一个已经发送过的token,但是可能误判一个每发送过的为发送了。此时发生了hash冲突。但是当hash空间一定大的时候,是可以降低冲突的。还有发送push,少发了几个是可以容忍的。(类似tair丢失极少量数据是可以容忍的)
使用
Guava的布隆过滤器通过调用BloomFilter
类的静态函数创建,传递一个Funnel
对象以及一个代表预期插入数量的整数。
Funnel
对象的作用,将数据发送给一个接收器(Slink
)。
package guava;
import com.google.common.base.Strings;import com.google.common.collect.Lists;import com.google.common.collect.Maps;import com.google.common.hash.BloomFilter;import com.google.common.hash.Funnel;import com.google.common.hash.PrimitiveSink;import org.junit.Before;import org.junit.Test;
import java.io.BufferedWriter;import java.io.File;import java.io.FileWriter;import java.io.IOException;import java.nio.charset.Charset;import java.util.List;import java.util.Map;import java.util.Random;
/** * Created by hgf on 16/8/25. */public class BloomFilterTest {
private BloomFilter<String> bloomFilter;
private final static String TOKEN = "token-[0-9]+";
private final static String prefix = "token-";
private List<String> tokens = Lists.newArrayList();
private Map<String, Integer> map = Maps.newHashMap();
private Map<String, Integer> reflect = Maps.newHashMap();
@Beforepublic void init() { Random random = new Random();
for (int i = 0; i < 10000; i++) { tokens.add(prefix + random.nextInt(10000)); }try { writeSource(); } catch (IOException e) { e.printStackTrace(); } }
@Testpublic void test() {//此处使用的是自定义funnel,可以使用guava默认实现的funnel。//全部实现在Funnels中。Funnels.stringFunnel(Charset.defaultCharset())//注意此处布隆过滤器大小,应估算的稍大 bloomFilter = BloomFilter.create(stringFunnel(), tokens.size());
int repeatTimes = 0;for (String token : tokens) {//参照数据 Integer tmp = reflect.get(token);if (tmp == null) { reflect.put(token, 1); } else { reflect.put(token, tmp + 1); }
//布隆过滤器数据boolean hasToken = bloomFilter.mightContain(token);if (hasToken) { Integer times = map.get(token);if (times == null) { map.put(token, 2); } else { map.put(token, times + 1); } repeatTimes++; } else { bloomFilter.put(token); } }
System.out.println("随机token中重复次数:" + repeatTimes);// //打印重复的token// System.out.println("bloom filter 判断为重复的token数:" + map);
compareResult(); }
private void compareResult() {
int wrongCount = 0;for (String token : map.keySet()) { Integer tmp = map.get(token);if (!(tmp != null && tmp.intValue() == reflect.get(token).intValue())) { wrongCount++; System.out.println("错误统计:\t" + token + "\t" + tmp + "\t" + reflect.get(token)); } } System.out.println("总统计出错,对比结果:" + wrongCount+"次"); }
private void writeSource() throws IOException { File file = new File("source.txt");try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(file))) {for (String token : tokens) { bufferedWriter.write(token); bufferedWriter.newLine(); } }
}
public static Funnel<String> stringFunnel() {return StringFunnel.INSTANCE; }
private enum StringFunnel implements Funnel<String> { INSTANCE;
@Overridepublic void funnel(String from, PrimitiveSink into) {if (isToken(from)) { into.putString(from, Charset.defaultCharset()); } }
private boolean isToken(String token) {return (!Strings.isNullOrEmpty(token) && token.matches(TOKEN)); }
}}
输出结果
随机token中重复次数:3709错误统计: token-7746 2 1错误统计: token-5714 2 1错误统计: token-1718 3 2错误统计: token-7065 3 2错误统计: token-8875 4 3错误统计: token-3599 2 1错误统计: token-8563 2 1总统计出错,对比结果:7次
注意点
预期插入数量是很关键的一个参数。当插入的数量接近或高于预期值的时候,布隆过滤器将会填满,这样的话,它会产生很多无用的误报点。
有另一个版本的 BloomFilter.create
方法,它额外接收一个参数,一个代表假命中概率水平的双精度数字(必须大于零且小于1)
假命中概率等级影响哈希表储存或搜索元素的数量。百分比越低,哈希表的性能越好。
场景
适用判断一个元素是否在某个集合(该集合往往数据量庞大)出现,并允许一定小概率错误的场景。
例如:判断一个url或者邮件地址或者token,是否出现在给定集合中。
做缓存的时候,使用bloomfilter,不给只访问过一次的数据做缓存,数据量大但是大多都只需要访问一次。
对大型分布式的NOSQL,使用布隆过滤器判断某一行数据是否存在,避免无谓的磁盘读写和查找。HBASE,BIGTABLE
判断恶意网址。
避免爬虫爬取同样的url,陷入爬取旋涡。
推荐网站避免给用户推送同样的url。
与Tair方案对比
优势:
判断重复节约大量内存空间。
劣势:
在分布式场景中,目前需要自己包装服务。
原文:https://my.oschina.net/hgfdoing/blog/739858
Guava 布隆过滤器相关推荐
- 布隆过滤器 - Redis 布隆过滤器,Guava 布隆过滤器 BloomFilter
文章目录 布隆过滤器 - Redis 布隆过滤器,Guava 布隆过滤器 BloomFilter 1.布隆过滤器的起源,用途 2.布隆过滤器的概念 3.布隆过滤器的优缺点 1.优点 2.缺点 4.应用 ...
- 布隆过滤器 Guava布隆过滤器的使用
简介 布隆过滤器(Bloom Filter)是1970年由一个叫Bloom的老哥提出的.本质上属于一种数据结构,实际组成是一个很长的二进制向量和一系列随机映射函数.布隆过滤器可以用于检索一个元素是否在 ...
- Redis Guava 布隆过滤器实现和准确率测试
测试准确率 布隆过滤器的作用 判断这个数据是否存在于我们的集合中存在就是1 不存在就是0 它的底层实现就是一个二进制的数列 原理, 我们都知道hash算法, 如同给我们的数据加入了一个指纹, 但是ha ...
- Guava布隆过滤器(boomfilter)使用简介
1 布隆过滤器简介 布隆过滤器是一种空间利用率较高的概率型数据结构,用来测试一个元素是否在集合中.但是存在一定可能,导致结果误判.即元素不在集合中,查询结果却返回元素在集合中. 布隆过滤器一些的性质 ...
- 【恋上数据结构】布隆过滤器(Bloom Filter)原理及实现
布隆过滤器(Bloom Filter) 引出布隆过滤器(判断元素是否存在) 布隆过滤器介绍(概率型数据结构) 布隆过滤器的原理(二进制 + 哈希函数) 布隆过滤器的误判率(公式) 布隆过滤器的实现 布 ...
- Redis缓存击穿解决办法之bloom filter布隆过滤器
转载地址:http://blog.jobbole.com/113396/ 布隆过滤器:Google Guava类库源码分析及基于Redis Bitmaps的重构 2017/12/30 · 开发 · B ...
- 【Linux服务器开发】1.4 布隆过滤器-判断海量数据中某条数据是否存在
目录 0.链接 1.问题的引入 2.看看是否有可供选择的数据结构来帮助我们判断在海量数据中某条数据是否存在 2.1 红黑树 2.1.1 从时间来分析 2.1.2 从空间来分析 2.2 哈希表 2.2 ...
- Redis缓存预热、缓存穿透、缓存击穿、缓存雪崩,Redis布隆过滤器怎么实现?
目录 一.缓存预热 1.缓存预热常见步骤 2.代码实现 二.缓存雪崩 1.什么情况会发生缓存雪崩? 2.Redis缓存集群实现高可用 3.如何避免Redis缓存雪崩? 三.缓存穿透 1.什么情况会发生 ...
- redis是整合google guava的布隆过滤器
为什么要使用布隆过滤器? 布隆过滤器(bloomfilter)有两大作用,一是为了防止缓存穿透,二是为了在百万级数据里快速高效的去重 什么是布隆过滤器? 1,布隆过滤器是用来判断一个元素是否出现在给定 ...
最新文章
- SAP QM MB56 报表没有结果之分析与对策
- python pymysql 多线程 读写数据库 报错 Packet sequence number wrong
- Excel 自动根据Sheet名生成目录
- sql三张表的搜索要满足5种搜索条件的模糊搜索_面试三轮我倒在了一道 SQL 题上……| 原力计划...
- SegmentFault 创始人祁宁对话 C# 之父 Anders Hejlsberg
- struts2面试问题_Struts2面试问答
- 软件测试中的存根程序
- minetest Window编译运行
- 电解槽行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
- Android系统启动-SystemServer下篇
- smokeping with tcpping centos 7 环境
- 边走边拍───南极三岛之行
- QQ空间说说批量删除
- Finecms基础操作手册
- 可靠的运输层协议,如何抵抗“剪网线”的降维打击?(rdt协议的的总结与思考)
- 漫画:什么是哥德巴赫猜想?
- MySQL向表中添加一个列
- 猿人学第4题(一天搞一个)
- SEO一场智慧心理之战
- 触动精灵获取某东代付的链接