背景
缓存是解决日常软件问题的重要概念。 您的应用程序可能会执行CPU密集型操作,而您又不想一次又一次地执行这些操作,而是只导出一次结果并将其缓存在内存中。 有时瓶颈是IO,例如您不想重复访问数据库,并且想缓存结果并仅在基础数据发生更改时才更新缓存。
同样,在其他一些用例中,我们需要执行快速查找来决定如何处理传入的请求。 例如,考虑这种用例,您必须识别一个URL是否指向恶意软件站点。 要在一个实例中执行此操作,可能会有很多类似的URL,如果我们将所有恶意软件URL缓存在内存中,则将需要大量空间来保存它们。 另一个用例可能是识别用户键入的字符串是否对美国的某个地方有任何引用。 就像“华盛顿博物馆”一样,华盛顿就是美国一个地方的名字。 我们应该把美国的所有地方都留在记忆中然后查找吗? 缓存大小有多大? 在没有任何数据库支持的情况下这样做是否有效?
这是我们需要脱离基本地图数据结构并在更高级的数据结构(例如Bloomfilter)中寻找答案的地方。 您可以考虑Bloomfilter,就像其他任何Java集合一样,您可以在其中放置项目,并询问它是否已存在某个项目(例如HashSet)。 如果Bloomfilter提到它不包含该项目,那么肯定不存在该项目。 但是,如果它提到看到了该项目,则可能是错误的。 如果我们足够谨慎的话,我们可以设计一个bloomfilter以便控制错误的可能性。
说明
Bloomfilter设计为m位的数组(A)。 最初,所有这些位都设置为0。
要添加项目:
为了添加任何项目,需要通过k个散列函数进行馈送。 每个哈希函数都会生成一个数字,该数字可以视为位数组的位置(哈希模数数组的长度可以为我们提供数组的索引),我们应将该位置的值设置为1。例如–第一个哈希函数(hash1)在项I上生成位位置x,类似地,第二和第三哈希函数生成位置y和z。
因此,我们将设置:
A[x]=A[y]=A[z] = 1
查找项目:
将重复类似的过程,将通过三个不同的哈希函数将项目哈希三次。 每个哈希函数将产生一个整数,该整数将被视为数组的位置。 我们将检查位数组的x,y,z位置,并查看它们是否设置为1。 如果不是,那么肯定没有人尝试将其添加到bloomfilter中,但是如果所有位都已设置,则可能是假阳性。
调整的东西
从上面的解释中可以清楚地看出,要设计一个好的bloomfilter,我们需要跟踪以下内容
- 良好的哈希函数可以尽快生成广泛的哈希值
- m的值(位阵列的大小)非常重要。 如果大小太小,则所有位将很快设置为1,并且误报会大大增加。
- 散列函数(k)的数量也很重要,这样值才能均匀分布。
如果我们可以估计计划在Bloom Bloom过滤器中保留多少个项目,则可以计算k和m的最佳值。 跳过数学上的细节,计算k和m的公式足以让我们编写一个良好的Bloomfilter。
确定m(布隆过滤器的位数)的公式如下:
m = - nlogp / (log2)^2;
其中p =期望的假阳性概率
确定k(哈希函数数)的公式如下:
k = m/n log(2) ;
其中k =哈希函数的数量,m =位数,n =过滤器中的项目数
散列是一个影响bloomfilter性能的区域。 我们需要选择一个有效但又不费时的哈希函数。 在“更少的哈希,相同的性能:构建更好的Bloom过滤器”一文中,讨论了如何使用两个哈希函数生成K个哈希函数。 首先,我们需要计算两个哈希函数h1(x)和h2(x)。 接下来,我们可以使用这两个哈希函数来模拟自然的k个哈希函数
gi(x) = h1(x) + ih2(x);
我可以在{1..k}范围内
Google番石榴库在其Bloomfilter实现中使用了该技巧,哈希逻辑在此处概述:
long hash64 = …; //calculate a 64 bit hash function//split it in two halves of 32 bit hash values int hash1 = (int) hash64; int hash2 = (int) (hash64 >>> 32);//Generate k different hash functions with a simple loopfor (int i = 1; i <= numHashFunctions; i++) {int nextHash = hash1 + i * hash2;}
应用领域
从数学公式可以明显看出,要使用Bloomfilter解决问题,我们需要非常了解该域。 就像我们可以应用Bloomfilter来保留美国所有城市的名称一样。 此数字是确定性的,我们具有先验知识,因此我们可以确定n(要添加到Bloomfilter的元素总数)。 根据业务需求固定p(假阳性概率)。 在那种情况下,我们有一个完美的缓存,可以提高内存效率,并且查找时间非常短。
实作
Google番石榴库具有Bloomfilter的实现。 检查此类的构造函数如何查询期望的项目和误报率。
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;//Create Bloomfilter
int expectedInsertions = ….;
double fpp = 0.03; // desired false positive probability
BloomFilter<CharSequence> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.forName("UTF-8")), expectedInsertions,fpp)
资源:
- http://en.wikipedia.org/wiki/Bloom_filter
- http://billmill.org/bloomfilter-tutorial/
- http://www.eecs.harvard.edu/~kirsch/pubs/bbbf/esa06.pdf
翻译自: https://www.javacodegeeks.com/2014/07/how-to-use-bloom-filter-to-build-a-large-in-memory-cache-in-java.html