BitSet

JAVA中BitSet就是“位图”数据结构,根据“位图”的语义,数据的存在性可以使用bit位上的1或0来表示;一个bit具有2个值:0和1,正好可以用来表示false和true。对于判断“数据是否存在”的场景,我们通常使用HashMap来存储,不过hashmap这个数据结构KEY和Value的保存需要消耗较多的内存,不适合保存较多的数据,即大数据场景;比如在有10亿条URL中判定一个“www.baidu.com/a”是否存在,如果我们使用常规的hashmap来保存将是不现实的,因为URL本身需要占据较多的内存而无法直接操作。如果我们使用bitset来保存,那么可以对一条URL求hashcode,并将数字映射在bitset上,那么事实上它只需要bitset上的一个bit位即可,即我们1位空间即可表达一个URL字符串的存在性。

 

    所谓“存在性”,就是通过BitSet来检测一个数字是否存在。

 

1、BitSet原理

    JAVA中,一个long型数字占用64位空间,根据上述“位图”的概念,那么一个long型数字就可以保存64个数字的“存在性”状态(无碰撞冲突时)。比如50个数字{0,1,10,...63},判定“15”是否存在,那么我们通常会首先将这些数字使用数组或者hashmap保存,然后再去判定,那么保存这些这些数据需要占用64 * 64位;如果使用位图,那么一个long型数字即可。(如果换成50个字符串,那么其节约的空间可能更大)。

 

    1) BitSet只面向数字比较,比如set(int a,boolean value)方法,将数字a在bitSet中设定为true或者false;此后可以通过get(int a)方法检测结果。对于string类型的数据,如果像使用BitSet,那么可以将其hashcode值映射在bitset中。

    2) 首先我们需要知道:1,1<<64,1<<128,1<<192...等,这些数字的计算结果是相等的(位运算);这也是一个long数字,只能表达连续的(或者无冲突的)64个数字的状态,即如果把数字1在long中用位表示,那么数字64将无法通过同一个long数字中位表示--冲突;BitSet内部,是一个long[]数组,数组的大小由bitSet接收的最大数字决定,这个数组将数字分段表示[0,63],[64,127],[128,191]...。即long[0]用来存储[0,63]这个范围的数字的“存在性”,long[1]用来存储[64,127],依次轮推,这样就避免了位运算导致的冲突。

Java代码  

  1. |------------|----------|----------|----------|----------|  
  2. |  
  3. |  数字范围      [0,63]     [64,127]  [128,191]     ...   |  
  4. |------------|----------|----------|----------|----------|  
  5. |  
  6. |long数组索引      0           1          2         ...   |  
  7. |------------|----------|----------|----------|----------|  

 

    3) bitSet内部的long[]数组是基于向量的,即随着set的最大数字而动态扩展。数组的最大长度计算:

Java代码  

  1. (maxValue - 1) >> 6  + 1  

 

    4) BitSet中set方法伪代码:

Java代码  

  1. public void set(int number) {  
  2.     int index = number >> 6;//找到number需要映射的数组的index。  
  3.     if(index + 1 > length) {  
  4.         ensureCapacity(index + 1);//重新扩展long[]数组  
  5.     }   
  6.     long[index] |= (1L << number);//冲突解决  
  7. }  

 

2、使用BitSet:本例中使用bitSet做String字符串的存在性校验。

Java代码  

  1. BitSet bitSet = new BitSet(Integer.MAX_VALUE);//hashcode的值域  
  2.   
  3. //0x7FFFFFFF  
  4. String url = "http://baidu.com/a";  
  5. int hashcode = url.hashCode() & 0x7FFFFFFF;  
  6. bitSet.set(hashcode);  
  7.   
  8. System.out.println(bitSet.cardinality());//着色位的个数  
  9. System.out.println(bitSet.get(hashcode));//检测存在性  
  10. bitSet.clear(hashcode);//清除位数据  

 

3、BitSet与Hashcode冲突

    因为BitSet API只能接收int型的数字,即只能判定int数字是否在bitSet中存在。所以,对于String类型,我们通常使用它的hashcode,但这有一种隐患,java中hashcode存在冲突问题,即不同的String可能得到的hashcode是一样的(即使不重写hashcode方法),如果我们不能很好的解决这个问题,那么就会出现“数据抖动”---不同的hashcode算法、运行环境、bitSet容量,会导致判断的结果有所不同。比如A、B连个字符串,它们的hashcode一样,如果A在BitSet中“着色”(值为true),那么检测B是否在BitSet存在时,也会得到true。

 

    这个问题该如何解决或者缓解呢?

    1)调整hashcode生成算法:我们可以对一个String使用多个hashcode算法,生成多个hashcode,然后在同一个BitSet进行多次“着色”,在判断存在性时,只有所有的着色位为true时,才判定成功。

Java代码  

  1. String url = "http://baidu.com/a";  
  2. int hashcode1 = url.hashCode() & 0x7FFFFFFF;  
  3. bitSet.set(hashcode1);  
  4.   
  5. int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;  
  6. bitSet.set(hashcode2);  
  7. System.out.println(bitSet.get(hashcode1) && bitSet.get(hashcode2));  

 

    其实我们能够看出,这种方式降低了误判的概率。但是如果BitSet中存储了较多的数字,那么互相覆盖着色,最终数据冲突的可能性会逐渐增加,最终仍然有一定概率的判断失误。所以在hashcode算法的个数与实际String的个数之间有一个权衡,我们建议:  “hashcode算法个数 * String字符串的个数”  < Integer.MAX_VALUE * 0.8

 

    2) 多个BitSet并行保存:

    改良1)中的实现方式,我们仍然使用多个hashcode生成算法,但是每个算法生成的值在不同的BitSet中着色,这样可以保持每个BitSet的稀疏度(降低冲突的几率)。在实际结果上,比1)的误判率更低,但是它需要额外的占用更多的内存,毕竟每个BitSet都需要占用内存。这种方式,通常是缩小hashcode的值域,避免内存过度消耗。

Java代码  

  1. BitSet bitSet1 = new BitSet(Integer.MAX_VALUE);//127M  
  2. BitSet bitSet2 = new BitSet(Integer.MAX_VALUE);  
  3.   
  4. String url = "http://baidu.com/a";  
  5. int hashcode1 = url.hashCode() & 0x7FFFFFFF;  
  6. bitSet1.set(hashcode1);  
  7.   
  8. int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;  
  9. bitSet2.set(hashcode2);  
  10.   
  11. System.out.println(bitSet1.get(hashcode1) && bitSet2.get(hashcode2));  

 

    3) 是否有必要完全避免误判?

    如果做到100%的正确判断率,在原理上说BitSet是无法做的,BitSet能够保证“如果判定结果为false,那么数据一定是不存在;但是如果结果为true,可能数据存在,也可能不存在(冲突覆盖)”,即“false == YES,true == Maybe”。有人提出将冲突的数据保存在类似于BTree的额外数据结构中,事实上这种方式增加了设计的复杂度,而且最终仍然没有良好的解决内存占用较大的问题。

 

4、BloomFilter(布隆姆过滤器)

    BloomFilter 的设计思想和BitSet有较大的相似性,目的也一致,它的核心思想也是使用多个Hash算法在一个“位图”结构上着色,最终提高“存在性”判断的效率。请参见Guava  BloomFilter。如下为代码样例:

Java代码  

  1. Charset charset = Charset.forName("utf-8");  
  2. BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(charset),2<<21);//指定bloomFilter的容量  
  3. String url = "www.baidu.com/a";  
  4. bloomFilter.put(url);  
  5. System.out.println(bloomFilter.mightContain(url));

原文链接:[http://wely.iteye.com/blog/2228771]

时间: 2024-09-19 18:19:54

BitSet的相关文章

[大数据量]BitMap即java.util.BitSet的应用

Bitmap算法, 问题:对40亿个数据进行排序,数据类型为 int,无相同数据. 思考:关于40亿个数据的排序,首先想如何存储呢?一个int 4个字节,也就是160亿个字节,也就是大概有16GB的数据,现在所有的计算机估计 没有这么大的内存吧,所以我们就可以文件归并排序,也可以分段读入数据在进行Qsort,但是都需要不停地读入文件,可以想象不停地读取文件硬件操作会有多么浪费时间.  我们这样都是用4个字节来存储了一个数据,在计算机里都是用二进制进行表示, 例如 5 :0000 0000 000

java中的BitSet

BitSet实际是由"二进制位"构成的一个Vector.如果希望高效率地保存大量"开-关"信息,就应使用BitSet.它只有从尺寸的角度看才有意义:如果希望的高效率的访问,那么它的速度会比使用一些固有类型的数组慢一些. 此外,BitSet的最小长度是一个长整数(Long)的长度:64位.这意味着假如我们准备保存比这更小的数据,如8位数据,那么BitSet就显得浪费了.所以最好创建自己的类,用它容纳自己的标志位. 在一个普通的Vector中,随我们加入越来越多的元素,

UVa 10651 Pebble Solitaire:DP&amp;amp;bitset

10651 - Pebble Solitaire Time limit: 3.000 seconds http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&page=show_problem&problem=1592 化为二进制进行状态转移,详见代码. 完整代码: /*0.016s*/ #include<bits/stdc++.h> using names

如何使用bitset实现二进制和十进制的相互转换

一.相互转换 01.#include<cstdio> 02.#include<cmath> 03.#include<cstring> 04.#include<cstdlib> 05.#include<string> 06.#include<bitset> 07.using namespace std; 08.const int Size = 32; 09. 10.char str[Size]; 11. 12.int main(void

C++标准库 bitset

有些程序要处理二进制位的有序集,每个位可能包含 0(关)1(开)值.位是用来保存一组项或条件 的 yes/no 信息(有时也称标志)的简洁方法.标准库提供的 bitset 类简化了位集的处理.要使用 bitset 类就必须包含相关的头文件.在本书提供的例子中,假设都使用 std::bitset 的using声明: #include <bitset> using std::bitset; bitset 对象的定义和初始化 下表列出了 bitset 的构造函数.类似于 vector,bitset

BitSet的使用场景及简单示例

BitSet简介     类实现了一个按需增长的位向量.位 set 的每个组件都有一个boolean值.用非负的整数将BitSet的位编入索引.可以对每个编入索引的位进行测试.设置或者清除.通过逻辑与.逻辑或和逻辑异或操作,可以使用一个BitSet修改另一个BitSet的内容.     默认情况下,set 中所有位的初始值都是false.     每个位 set 都有一个当前大小,也就是该位 set 当前所用空间的位数.注意,这个大小与位 set 的实现有关,所以它可能随实现的不同而更改.位 s

[面试经]STL bitset用法总结

声明 #include < bitset > using std::bitset; bitset的定义和初始化 bitset<32> bitvec; //32位,全为0. 给出的长度值必须是常量表达式.正如这里给出的,长度值必须定义为整型字面值常量或是已用常量值初始化的整数类型的const对象. 这条语句把bitvec定义为含有32个位的bitset对象.和vector的元素一样,bitset中的位是没有命名的,程序员只能按位置来访问它们.位集合的位置编号从0开始,因此,bitve

std::bitset

    [转]http://www.cnblogs.com/bless/archive/2008/08/10/1264549.html 有些程序要处理二进制位的有序集,每个位可能包含的是0(关)或1(开)的值.位是用来保存一组项或条件的yes/no信息(有时也称标志)的简洁方法.标准库提供了bitset类使得处理位集合更容易一些.要使用bitset类就必须要包含相关的头文件.在本书提供的例子中,假设都使用了std::bitset的using声明: Code#include <bitset>us

把《c++ primer》读薄(3-3 标准库bitset类型)

督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. //开头 #include <bitset> using std::bitset; 问题1.标准库bitset类型(模版) 需要处理二进制位的时候,可以使用c++标准库提供的bitset类型,它也是类模版,类似vectro容器,唯一不同的是,bitset类型需要说明长度,使用常量表达式给出的整型字面值或者已经初始化的cosnt对象. bitset<32> bit;//从0到31位算的,bit的32位每位初始化为

【转载】BitSet

本文转载自http://shift-alt-ctrl.iteye.com/blog/2194519   JAVA中BitSet就是"位图"数据结构,根据"位图"的语义,数据的存在性可以使用bit位上的1或0来表示:一个bit具有2个值:0和1,正好可以用来表示false和true.对于判断"数据是否存在"的场景,我们通常使用HashMap来存储,不过hashmap这个数据结构KEY和Value的保存需要消耗较多的内存,不适合保存较多的数据,即大数