BitMap BloomFilter BitSet详解

1. BitMap

BitMap利用内存中连续的二进制位,用于对大量数据做去重和查询
例如,给定一块长度为10bit的内存空间,想要依次插入整形数据4,2,1,3,我们按照如下的步骤来做:
在这里插入图片描述

package BitMap;
/**
 * 位图BitMap实现代码
 * @author xjh 2010.01.18
 */
public class bitmap {
    public char[] bytes;
    public int nbits;

    public bitmap(int nbits){
        this.nbits=nbits;
        this.bytes=new char[nbits/8+1];
            //一个byte占8位 换言之1byte=8bit
    }

    public void set(int k){
        if (k>nbits) return;
        int byteIndex=k/8;
        int bitIndex=k%8;
        bytes[byteIndex]|=(1<<bitIndex);    //1转成二进制 然后左移bitIndex位
    }

    public boolean get(int k){
        if (k>nbits) return false;
        int byteIndex=k/8;
        int bitIndex=k%8;
        return (bytes[byteIndex]&(1<<bitIndex))!=0;
    }
}

关于BitMap的案例,这里推荐程序员小灰的一篇博客:https://mp.weixin.qq.com/s/xxauNrJY9HlVNvLrL5j2hg

2. Bloom Filter

在这里插入图片描述
布隆过滤器的误判有一个特点,它只对存在的情况有误判。如果某个数字经过布隆过滤器判断不存在,那说明这个数字真的不存在,不会发生误判;如果某个数字经过布隆过滤器判断存在,这个时候可能有误判,有可能是不存在的。不过,只要我们调正哈希函数的个数、位图大小跟要存储数字的个数之间的比例,那可以将这种误判的概率讲到非常低。
Google的Guava工具提供了BloomFilter的实现。

总结:常用于大数据的排重,比如email,url 等。 核心原理:将每条数据通过计算产生一个指纹(一个字节或多个字节,但一定比原始数据要少很多),其中每一位都是通过随机计算获得,在将指纹映射到一个大的按位存储的空间中。注意:会有一定的错误率。
优点:空间和时间效率都很高。 布隆过滤器存储空间和插入 / 查询时间都是常数O(k)。另外, 散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。
缺点:随着存入的元素数量增加,误算率随之增加。

关于bloomFilter在url查重中的运用,这里推荐一篇博客:网络爬虫:URL去重策略之布隆过滤器(BloomFilter)的使用

3.BitSet

原理简介:
Java平台的BitSet用于存放一个位序列,如果要高效的存放一个位序列,就可以使用位集(BitSet)。由于位集将位包装在字节里,所以使用位集比使用Boolean对象的List更加高效和更加节省存储空间。
BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,一次扩充64位,最终内部是由N个long来存储
默认情况下,BitSet的所有位都是false即0。
在没有外部同步的情况下,多个线程操作一个BitSet是不安全的

一个1GB的空间,有8102410241024 = 8.5810^9bit,也就是1GB的空间可以表示85亿多个数。

应用场景:

  1. 统计一组大数据中没有出现过的数;
     将这组数据映射到BitSet,然后遍历BitSet,对应位为0的数表示没有出现过的数据。
    
  2. 对大数据进行排序;
     将数据映射到BitSet,遍历BitSet得到的就是有序数据。
    
  3. 在内存对大数据进行压缩存储等等。
     一个GB的内存空间可以存储85亿多个数,可以有效实现数据的压缩存储,节省内存空间开销。
    

为什么BitSet使用long数组做内部存储?
JDK选择long数组作为BitSet的内部存储结构是出于性能的考虑,因为BitSet提供and和or这种操作,需要对两个BitSet中的所有bit位做and或者or,实现的时候需要遍历所有的数组元素。使用long能够使得循环的次数降到最低,所以Java选择使用long数组作为BitSet的内部存储结构。
从数据在栈上的存储来说,使用long和byte基本是没有什么差别的,除了编译器强制地址对齐的时候,使用byte最多会浪费7个字节(强制按照8的倍数做地址对其),另外从内存读数组元素的时候,也是没有什么区别的,因为汇编指令有对不同长度数据的mov指令。所以说,JDK选择使用long数组作为BitSet的内部存储结构的根本原因就是在and和or的时候减少循环次数,提高性能。
例如我们进行BitSet中的and, or,xor操作时,要对整个bitset中的bit都进行操作,需要依次读出bitset中所有的word,如果是long数组存储,我们可以每次读入64个bit,而int数组存储时,只能每次读入32个bit。另外我们在查找bitset中下一个置为1的bit时,word首先会和0进行比较,如果word的值为0,则表示该word中没有为1的bit,可以忽略这个word,如果是long数组存储,可以一次跳过64个bit,如果是int数组存储时,一次只能跳过32个bit。

参考文献:
1.Java BitSet(位集)
2.大量数据去重:Bitmap和布隆过滤器(Bloom Filter)

发布了65 篇原创文章 · 获赞 20 · 访问量 1万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章