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亿多个数。
应用场景:
-
统计一组大数据中没有出现过的数; 将这组数据映射到BitSet,然后遍历BitSet,对应位为0的数表示没有出现过的数据。
-
对大数据进行排序; 将数据映射到BitSet,遍历BitSet得到的就是有序数据。
-
在内存对大数据进行压缩存储等等。 一个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。