通过前一篇文章的学习,对于 BloomFilter 的概念和原理。以及误报率等计算方法都一个理性的认识了。

在这里,我们将用 Java'实现一个简单的 BloomFilter 。

package pri.xiaoye.day1029;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.BitSet;
import java.util.Collection; /**
* 布隆过滤器的实现类
* 定义:http://en.wikipedia.org/wiki/Bloom_filter
*
* @param <E>
* E指定了要插入过滤器中的元素的类型。eg,String Integer
* @author Magnus Skjegstad <magnus@skjegstad.com>
* @translator xiaoye
*/
public class BloomFilter<E> implements Serializable { private static final long serialVersionUID = -2326638072608273135L;
private BitSet bitset;
private int bitSetSize;
private double bitsPerElement;
private int expectedNumberOfFilterElements;//可以加入的元素的最大个数
private int numberOfAddedElements;//过滤器容器中元素的实际数量
private int k; // 哈希函数的个数 static final Charset charset = Charset.forName("UTF-8");//存储哈希值的字符串的编码方式 static final String hashName = "MD5"; //在大多数情况下,MD5提供了较好的散列准确度。如有必要,可以换成 SHA1算法
static final MessageDigest digestFunction;//MessageDigest类用于为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法
static { // 初始化 MessageDigest 的摘要算法对象
MessageDigest tmp;
try {
tmp = java.security.MessageDigest.getInstance(hashName);
} catch (NoSuchAlgorithmException e) {
tmp = null;
}
digestFunction = tmp;
} /**
* 构造一个空的布隆过滤器. 过滤器的长度为c*n
* @param c
* 表示每一个元素占有多少位
* @param n
* 表示过滤器能加入的最大元素数量
* @param k
* 表示须要使用的哈希函数的个数
*/
public BloomFilter(double c, int n, int k) {
this.expectedNumberOfFilterElements = n;
this.k = k;
this.bitsPerElement = c;
this.bitSetSize = (int) Math.ceil(c * n);
numberOfAddedElements = 0;
this.bitset = new BitSet(bitSetSize);
} /**
* 构造一个空的布隆过滤器。最优哈希函数的个数将由过滤器的总大小和期望元素个数来确定。
*
* @param bitSetSize
* 指定了过滤器的总大小
* @param expectedNumberOElements
* 指定了过滤器能加入的最大的元素数量
*/
public BloomFilter(int bitSetSize, int expectedNumberOElements) {
this(bitSetSize / (double) expectedNumberOElements, expectedNumberOElements, (int) Math.round((bitSetSize / (double) expectedNumberOElements)* Math.log(2.0)));
} /**
* 通过指定误报率来构造一个过滤器。 * 每一个元素所占的位数和哈希函数的数量会依据误报率来得出。
*
* @param falsePositiveProbability
* 所期望误报率.
* @param expectedNumberOfElements
* 要加入的元素的数量
*/
public BloomFilter(double falsePositiveProbability, int expectedNumberOfElements) {
this(Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2)))/ Math.log(2), // c = k/ln(2)
expectedNumberOfElements,
(int) Math.ceil(-(Math.log(falsePositiveProbability) / Math.log(2)))); // k = ln(2)m/n
} /**
* 依据旧过滤器的数据。又一次构造一个新的过滤器
*
* @param bitSetSize
* 指定了过滤器所需位的大小
* @param expectedNumberOfFilterElements
* 指定了过滤器所能加入的元素的最大数量
* to contain.
* @param actualNumberOfFilterElements
* 指定了原来过滤器的数据的数量
* <code>filterData</code> BitSet.
* @param filterData
* 原有过滤器中的BitSet对象
*/
public BloomFilter(int bitSetSize, int expectedNumberOfFilterElements,
int actualNumberOfFilterElements, BitSet filterData) {
this(bitSetSize, expectedNumberOfFilterElements);
this.bitset = filterData;
this.numberOfAddedElements = actualNumberOfFilterElements;
} /**
* 依据字符串的内容生成摘要
*
* @param val
* 字符串的内容
* @param charset
* 输入数据的编码方式
* @return 输出为一个long类型
*/
public static long createHash(String val, Charset charset) {
return createHash(val.getBytes(charset));
} /**
* 依据字符串内容生成摘要
*
* @param val
* 指定了输入的字符串。默认的编码为 UTF-8
* @return 输出为一个long类型
*/
public static long createHash(String val) {
return createHash(val, charset);
} /**
* 依据字节数组生成摘要
*
* @param data
* 输入数据
* @return 输出为long类型的摘要
*/
public static long createHash(byte[] data) {
long h = 0;
byte[] res; synchronized (digestFunction) {
res = digestFunction.digest(data);
} for (int i = 0; i < 4; i++) {
h <<= 8;
h |= ((int) res[i]) & 0xFF;
}
return h;
} /**
* 重写equals方法
*/
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final BloomFilter<E> other = (BloomFilter<E>) obj;
if (this.expectedNumberOfFilterElements != other.expectedNumberOfFilterElements) {
return false;
}
if (this.k != other.k) {
return false;
}
if (this.bitSetSize != other.bitSetSize) {
return false;
}
if (this.bitset != other.bitset
&& (this.bitset == null || !this.bitset.equals(other.bitset))) {
return false;
}
return true;
} /**
* 重写了hashCode方法
*
*/
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + (this.bitset != null ? this.bitset.hashCode() : 0);
hash = 61 * hash + this.expectedNumberOfFilterElements;
hash = 61 * hash + this.bitSetSize;
hash = 61 * hash + this.k;
return hash;
} /**
* 依据最大元素数量和过滤器的大小来计算误报率。 * 方法的返回值为误报率。假设插入的元素个数小于最大值,则误报率会比返回值要小。
*
* @return 期望的误报率.
*/
public double expectedFalsePositiveProbability() {
return getFalsePositiveProbability(expectedNumberOfFilterElements);
} /**
* 通过插入的元素数量和过滤器容器大小来计算实际的误报率。
*
* @param numberOfElements
* 插入的元素的个数.
* @return 误报率.
*/
public double getFalsePositiveProbability(double numberOfElements) {
// (1 - e^(-k * n / m)) ^ k
return Math.pow((1 - Math.exp(-k * (double) numberOfElements
/ (double) bitSetSize)), k); } /**
* 通过实际插入的元素数量和过滤器容器大小来计算实际的误报率。 *
* @return 误报率.
*/
public double getFalsePositiveProbability() {
return getFalsePositiveProbability(numberOfAddedElements);
} /**
* 返回哈希函数的个数 k
*
* @return k.
*/
public int getK() {
return k;
} /**
* 清空过滤器元素
*/
public void clear() {
bitset.clear();
numberOfAddedElements = 0;
} /**
* 向过滤器中加入元素。
* 加入的元素的toString()方法将会被调用。返回的字符串作为哈希函数的输出。 *
* @param element
* 要加入的元素
*/
public void add(E element) {
long hash;
String valString = element.toString();
for (int x = 0; x < k; x++) {
hash = createHash(valString + Integer.toString(x));
hash = hash % (long) bitSetSize;
bitset.set(Math.abs((int) hash), true);
}
numberOfAddedElements++;
} /**
* 加入一个元素集合到过滤器中
*
* @param c
* 元素集合.
*/
public void addAll(Collection<? extends E> c) {
for (E element : c)
add(element);
} /**
* 用来推断元素是否在过滤器中。 假设已存在。返回 true。
*
* @param element
* 要检查的元素.
* @return 假设预计该元素已存在。则返回true
*/
public boolean contains(E element) {
long hash;
String valString = element.toString();
for (int x = 0; x < k; x++) {
hash = createHash(valString + Integer.toString(x));
hash = hash % (long) bitSetSize;
if (!bitset.get(Math.abs((int) hash)))
return false;
}
return true;
} /**
* 推断一个集合中的元素是否都在过滤器中。 *
* @param c
* 要检查的元素集合
* @return 假设集合全部的元素都在过滤器中。则返回true。
*/
public boolean containsAll(Collection<? extends E> c) {
for (E element : c)
if (!contains(element))
return false;
return true;
} /**
* 得到某一位的值
*
* @param bit
* bit的位置.
* @return 假设该位被设置,则返回true。 */
public boolean getBit(int bit) {
return bitset.get(bit);
} /**
* 设置过滤器某一位的值
*
* @param bit
* 要设置的位置.
* @param value
* true表示已经成功设置。 false表示改为被清除。
*/
public void setBit(int bit, boolean value) {
bitset.set(bit, value);
} /**
* 返回存放信息的位数组.
*
* @return 位数组.
*/
public BitSet getBitSet() {
return bitset;
} /**
* 得到过滤器中位数组个大小。
*
* @return 数组大小.
*/
public int size() {
return this.bitSetSize;
} /**
* 返回已加入的元素的个数
*
* @return 元素个数.
*/
public int count() {
return this.numberOfAddedElements;
} /**
* 得到能加入的元素的最大数量
*
* @return 最大数量.
*/
public int getExpectedNumberOfElements() {
return expectedNumberOfFilterElements;
} /**
* 得到每一个元素占用的位的个数的期望值
*
* @return 每一个元素占用的位数
*/
public double getExpectedBitsPerElement() {
return this.bitsPerElement;
} /**
* 得到每一个元素占用位数的实际值
*
* @return 每一个元素占用的位数.
*/
public double getBitsPerElement() {
return this.bitSetSize / (double) numberOfAddedElements;
}
}

浅谈BloomFilter【下】用Java实现BloomFilter的更多相关文章

  1. [原创]浅谈NT下Ring3无驱进入Ring0的方法

    原文链接:浅谈NT下Ring3无驱进入Ring0的方法 (测试环境:Windows 2000 SP4,Windows XP SP2.Windows 2003 未测试) 在NT下无驱进入Ring0是一个 ...

  2. 浅谈 IE下innerHTML导致的问题

    原文:浅谈 IE下innerHTML导致的问题 先来看个demo吧: <!DOCTYPE html> <html> <head> <meta charset= ...

  3. []转帖] 浅谈Linux下的五种I/O模型

    浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html  一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是 ...

  4. 浅谈Vue下的components模板

    浅谈Vue下的components模板在我们越来越深入Vue时,我们会发现我们对HTML代码的工程量会越来越少,今天我们来谈谈Vue下的 components模板的 初步使用方法与 应用 我们先来简单 ...

  5. 浅谈Linux下/etc/passwd文件

    浅谈Linux 下/etc/passwd文件 看过了很多渗透测试的文章,发现在很多文章中都会有/etc/passwd这个文件,那么,这个文件中到底有些什么内容呢?下面我们来详细的介绍一下. 在Linu ...

  6. 浅谈Linux下如何修改IP

    linux 下命令之浅谈//cd ..  //返回上一级//创建文件夹touch test.txt//Linux不区分大小写//往一个文件中追加内容echo "****" > ...

  7. 浅谈《think in java》:一 对象导论总结

    清单1. 抽象机制,面向对象程序设计方式 java所基于Smalltalk的特性表现一种纯粹的面向对象设计方式: 万物都是对象 程序是对象的集合(容器),他们通过发送消息(发送请求)来告知彼此所要做的 ...

  8. 浅谈 Objective-C 下对象的初始化

    转自:http://www.oschina.net/question/54100_32468 众所周知,Objective-C是一门面向对象的语言,一般情况下,我们在Objective-C中定义一个类 ...

  9. 浅谈linux 下,利用Nginx服务器代理实现ajax跨域请求。

    ajax跨域请求对于前端开发者几乎在任何一个项目中都会用到,众所周知,跨域请求有三种方式: jsonp; XHR2 代理: jsonp: 这种应该是开发中是使用的最多的,最常见的跨域请求方法,其实aj ...

  10. 浅谈Android下的Bitmap之大Bitmap加载

    引言 我们常常提到的“Android程序优化”,通常指的是性能和内存的优化,即:更快的响应速度,更低的内存占用.Android程序的性能和内存问题,大部分都和图片紧密相关,而图片的加载在很多情况下很用 ...

随机推荐

  1. Web框架之Django_01初识(三大主流web框架、Django安装、Django项目创建方式及其相关配置、Django基础三件套:HttpResponse、render、redirect)

    摘要: Web框架概述 Django简介 Django项目创建 Django基础必备三件套(HttpResponse.render.redirect) 一.Web框架概述: Python三大主流Web ...

  2. shell基础学习-难点重点学习

    来自shell13问 -e : 啟用反斜線控制字符的轉換(參考下表) -E:關閉反斜線控制字符的轉換(預設如此) -n : 取消行末之換行符號(與 -e 選項下的 \c 字符同意) 要取消一個变量,在 ...

  3. MPEG-4与H.264的区别 , 编码 以及 应用

    MPEG4是适用于监控领域的压缩技术 MPEG4于1998年11月公布,原预计1999 年1月投入使用的国际标准MPEG4不仅是针对一定比特率下的视频.音频编码,更加注重多媒体系统的交互性和灵活性.M ...

  4. GitHub中国区前100名到底是什么样的人?(转载)

    本文根据Github公开API,抓取了地址显示China的用户,根据粉丝关注做了一个排名,分析前一百名的用户属性,剖析这些活跃在技术社区的牛人到底是何许人也!后续会根据我的一些经验出品<技术人员 ...

  5. NYOJ 232 How to eat more Banana

    How to eat more Banana 时间限制:1000 ms  |  内存限制:65535 KB 难度:4   描述 A group of researchers are designing ...

  6. 编辑被标记为“只读”的Word文档

    从邮件接收到的Word文档,打开时总是被标记为“只读”,在阅读时对其进行编辑,但不能保存,会提示文档为只读的.要想对其进行编辑并保存,需要进行一定的操作. 进入文件所在的目录,鼠标右键点击Word文档 ...

  7. 九度oj 题目1447:最短路

    题目描述: 在每年的校赛里,所有进入决赛的同学都会获得一件很漂亮的t-shirt.但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候,却是非常累的!所以现在他们想要寻找最短的从商店到赛场的路线 ...

  8. BZOJ 4566 [Haoi2016]找相同字符 ——广义后缀自动机

    建立广义后缀自动机. 然后统计子树中的siz,需要分开统计 然后对(l[i]-l[fa[i]])*siz[i][0]*siz[i][1]求和即可. #include <cstdio> #i ...

  9. Tomcat 调优技巧

    Tomcat 调优技巧:1.Tomcat自身调优: ①采用动静分离节约Tomcat的性能: ②调整Tomcat的线程池: ③调整Tomcat的连接器: ④修改Tomcat的运行模式: ⑤禁用AJP连接 ...

  10. K大数查询(bzoj 3110)

    Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置 ...