浅谈BloomFilter【下】用Java实现BloomFilter

通过前一篇文章的学习,对于 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 <[email protected]>
 * @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;
    }
}
时间: 2024-08-01 09:13:01

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

浅谈Linux下Makefile编写

浅谈Linux下Makefile的编写 前言:本文简要介绍Makefile文件的编写规范,结合具体项目中的应用进行讲解. 具体代码地址: https://github.com/AnSwErYWJ/DogFood/blob/master/Makefile 简介 Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作.而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关

浅谈 js 下 with 对性能的影响

这几天多次看到有博主们在写 with 的文章,这货确实非常方便,但是却是个性能杀手,所以一直都是上不得台面的.那么他究竟会让效率低下到什么程度呢?先来看下 with 是如何的便捷吧.. // 正常调用 console.log(location.host); console.log(location.pathname); // 在 with 下 with (location) { console.log(host); console.log(pathname); } 如果不影响性能,确实是非常霸气

浅谈 IE下innerHTML导致的问题

原文:浅谈 IE下innerHTML导致的问题 先来看个demo吧: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" content="http://www.softwhy.com/" /> <title>蚂蚁部落</title> <script type=&

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

浅谈Linux下的五种I/O模型 https://www.cnblogs.com/chy2055/p/5220793.html  一.关于I/O模型的引出 我们都知道,为了OS的安全性等的考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer.如下图所示: 整个请求过程为: 用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数

浅谈Linux下VIM配置

vim是从 vi 发展出来的一个文本编辑器.代码补全.编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用,和Emacs并列成为类Unix系统用户最喜欢的文本编辑器.初识Linux,对于很多东西还在学习中.但是作为一个熟悉了VS编译环境的菜鸟来说,面对Linux的界面只有吐槽了...去网上搜索了一些关于vim配置的相关文档,自己进行了一些配置,当然还不禁全面,以后可以继续进行配置.下面就将自己的配置分享给大家,希望可以由一点帮助. Centos里的VI只默认安装了vim-minimal-

浅谈Struts2下

计应134(实验班) 张人川 一.文件上传 在WEB-INFO/lib下加入commons-flileupload-1.2.1.jar,commons-io-1.3.2.jar 把form表的enctype设置为”multipart/form-data”如下 <form enctype=”multipart/form-data” action=”${pageContext.request.contextPath}/ xxx.action” method=”post”> <input ty

浅谈:合格的Java程序员应该具备的能力

和大家分享下合格的Java程序员在工作中都需要具备哪些能力呢? 一.文档习惯 良好的文档是正规研发流程中非常重要的环节,作为Java程序员,30%的工作时间写技术文档是很正常的,而作为高级程序员和系统分析员,这个比例还要占的更高. 二.规范化,标准化的代码编写习惯 一些外国知名软件公司的规矩,Java代码的变量命名,代码内注释格式,甚至嵌套中行缩进的长度和函数间的空行数字都有明确规定,良好的编写习惯,不但有助于Java代码的移植和纠错,也有助于不同技术人员之间的协作. 三.测试习惯 软件研发作为

浅谈terminal下快速开启sublime text3

如何在命令行中快速开启sublime text3打开当前文件夹呢? mac下配置如下:1.open .zshrc 2. alias subl="'/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl'" alias nano="subl"                             export EDITOR="subl" 附:rvm与zsh问题: This so

[原创]浅谈Linux下的rpm

虽然现在很多人都使用yum去替代rpm了,但是rpm在一些特殊场合下还是有其作用的,比如查询跟验证已安装的rpm包,rpm全称Redhat Package Manager,是一种用于互联网下载包的打包及安装工具,它包含在某些Linux分发版中,它生成具有.RPM扩展名的文件,与Dpkg类似. rpm的常用功能分两大块: (1)查询/核实软件包是否被安装:rpm {-q|--query} [select-options] [query-options]         (用的多) rpm {-V|

浅谈Linux下shell的入门与基础

1:什么是shellshell的本意是"壳"的意思,其实已经很形象地说明了shell在Linux系统中的作用.shell就是围绕在Linux内核之外的一个"壳"程序,用户在操作系统上完成的所有任务都是通过shell与Linux系统内核的交互来实现的.我们应该熟悉DOS系统中command.com程序,shell的功能与此类似,但是shell的功能更加强大,更加好用. 各种操作系统都有自己的shell.以DOS为例,它的shell就是command.com程序.DOS