zoukankan      html  css  js  c++  java
  • 布隆过滤器(Bloom Filter)

    背景

    我们以网络爬虫为例。网络间的链接错综复杂,爬虫程序在网络间“爬行”很可能会形成“环”。为了避免形成“环”,程序需要知道已经访问过网站的URL。当程序又遇到一个网站,根据它的URL,怎么判断是否已经访问过呢?

     第一个想法就是将已有URL放置在HashSet中,然后利用HashSet的特性进行判断。它只花费O(1)的时间。但是,该方法消耗的内存空间很大,就算只有1亿个URL,每个URL只算50个字符,就需要大约5GB内存。

     如何减少内存占用呢?URL可能太长,我们使用MD5等单向哈希处理后再存到HashSet中吧,处理后的字段只有128Bit,这样可以节省大量的空间。我们的网络爬虫程序又可以继续执行了。

     但是好景不长,网络世界浩瀚如海,URL的数量急速增加,以128bit的大小进行存储也要占据大量的内存。

     这种情况下,我们还可以使用BitSet,使用哈希函数将URL处理为1bit,存储在BitSet中。但是,哈希函数发生冲突的概率比较高,若要降低冲突概率到1%,就要将BitSet的长度设置为URL个数的100倍。

     但是冲突无法避免,这就带来了误判。理想中的算法总是又准确又快捷,但是现实中往往是“一地鸡毛”。我们真的需要100%的正确率吗?如果需要,时间和空间的开销无法避免;如果能够忍受低概率的错误,就有极大地降低时间和空间的开销的方法。 


    布隆过滤器

    布隆过滤器(Bloom Filter)是1970年由布隆提出的。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率。主要应用场景:网页URL的去重,垃圾邮件的判别,集合重复元素的判别,查询加速(比如基于key-value的存储系统)等。

    • 初始状态时,Bloom Filter是一个包含m位的位数组(bit-array),初始时候每一位都为0:

    • Bloom Filter使用k个相互独立的哈希函数,通过哈希函数将每个元素映射到{1,…,m}的范围中。比如当来了一个元素 a,进行判断,使用2个哈希函数,计算出该元素对应的Hash值为1和5,然后到Bloom Filter中判断第1位和第5位的值,上面全部为0,则a不在Bloom Filter内,将 a 添加进去,也就是将第1位和第5位的值从0修改为1。(布隆过滤器不需要存储元素本身)

    bloomfilter

    • 之后再来的元素,要判断是不是在Bloom Filter内,也是同a一样的方法,通过K个hash函数计算对应的hash值,然后去相应的位上查看是否都是1,只有都是 1 才认为这个元素在集合内,如果有一个为0则不存在,都为1则极为可能存在:

    bloomfilter

    • 随着元素的插入,Bloom filter 中修改的值变多,出现误判的几率也随之变大,当新来一个元素时,满足其在Bloom Filter内的条件,即所有对应位都是 1 ,这样就可能有两种情况,一是这个元素就在集合内,没有发生误判;还有一种情况就是发生误判,出现了哈希碰撞,这个元素本不在集合内。

    bloomfilter

    使用BloomFilter,有三个重要的值,错误率(false positive rate)、哈希函数个数以及BloomFilter位数组的大小,关于这三个值的最优配置有一个原则,(BloomFilter位数组大小)/(实际的元素个数)越大,错误率越低,但消耗的空间会越多。

    实现

    通过 Java 编程手动实现布隆过滤器

    我们上面已经说了布隆过滤器的原理,知道了布隆过滤器的原理之后就可以自己手动实现一个了。

    如果你想要手动实现一个的话,你需要:

    1. 一个合适大小的位数组保存数据
    2. 几个不同的哈希函数
    3. 添加元素到位数组(布隆过滤器)的方法实现
    4. 判断给定元素是否存在于位数组(布隆过滤器)的方法实现。

    下面给出一个我觉得写的还算不错的代码(参考网上已有代码改进得到,对于所有类型对象皆适用):

    import java.util.BitSet;
    
    public class MyBloomFilter {
    
        /**
         * 位数组的大小
         */
        private static final int DEFAULT_SIZE = 2 << 24;
        /**
         * 通过这个数组可以创建 6 个不同的哈希函数
         */
        private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134};
    
        /**
         * 位数组。数组中的元素只能是 0 或者 1
         */
        private BitSet bits = new BitSet(DEFAULT_SIZE);
    
        /**
         * 存放包含 hash 函数的类的数组
         */
        private SimpleHash[] func = new SimpleHash[SEEDS.length];
    
        /**
         * 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样
         */
        public MyBloomFilter() {
            // 初始化多个不同的 Hash 函数
            for (int i = 0; i < SEEDS.length; i++) {
                func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]);
            }
        }
    
        /**
         * 添加元素到位数组
         */
        public void add(Object value) {
            for (SimpleHash f : func) {
                bits.set(f.hash(value), true);
            }
        }
    
        /**
         * 判断指定元素是否存在于位数组
         */
        public boolean contains(Object value) {
            boolean ret = true;
            for (SimpleHash f : func) {
                ret = ret && bits.get(f.hash(value));
            }
            return ret;
        }
    
        /**
         * 静态内部类。用于 hash 操作!
         */
        public static class SimpleHash {
    
            private int cap;
            private int seed;
    
            public SimpleHash(int cap, int seed) {
                this.cap = cap;
                this.seed = seed;
            }
    
            /**
             * 计算 hash 值
             */
            public int hash(Object value) {
                int h;
                return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = value.hashCode()) ^ (h >>> 16)));
            }
    
        }
    }

    测试:

     String value1 = "https://javaguide.cn/";
            String value2 = "https://github.com/Snailclimb";
            MyBloomFilter filter = new MyBloomFilter();
            System.out.println(filter.contains(value1));
            System.out.println(filter.contains(value2));
            filter.add(value1);
            filter.add(value2);
            System.out.println(filter.contains(value1));
            System.out.println(filter.contains(value2));

    Output:

    false
    false
    true
    true

    测试:

    Integer value1 = 13423;
            Integer value2 = 22131;
            MyBloomFilter filter = new MyBloomFilter();
            System.out.println(filter.contains(value1));
            System.out.println(filter.contains(value2));
            filter.add(value1);
            filter.add(value2);
            System.out.println(filter.contains(value1));
            System.out.println(filter.contains(value2));

    Output:

    false
    false
    true
    true

    利用Google开源的 Guava中自带的布隆过滤器

    在Google Guava library中Google提供了一个布隆过滤器的实现:com.google.common.hash.BloomFilter

    https://github.com/google/guava/blob/master/guava/src/com/google/common/hash/BloomFilter.java

    布隆过滤器在大数据下的应用

    比如hbase通过rowkey查询时候,使用布隆过滤器快速判断该rowkey是否存在于当前storefile,再比如hive orc结合布隆过滤器,判断某个stripe下是否存在要查询的字段

    参考:
    https://juejin.im/post/5cc5aa7ce51d456e431adac5
    http://lxw1234.com/archives/2015/12/580.htm

    https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md

  • 相关阅读:
    python中拆分和拼接字符串
    python中find查找是否包含特定字符并返回第一个索引
    python中删除字符串左右的空格
    python中指定字符串宽度,并指定填充字符串
    python中将字符串中的制表符转换为空格
    python中统计字符串中特定字符出现的次数,内置方法count
    python中指定分隔符分割字符串,返回三元组
    学习笔记:社会科学的特质
    启示——来自《DOOM启世录》
    多语言处理 > UNICODE > IBM的ICU类库
  • 原文地址:https://www.cnblogs.com/zz-ksw/p/12161160.html
Copyright © 2011-2022 走看看