本文转载自http://shift-alt-ctrl.iteye.com/blog/2194519

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. //0x7FFFFFFF
  3. String url = "http://baidu.com/a";
  4. int hashcode = url.hashCode() & 0x7FFFFFFF;
  5. bitSet.set(hashcode);
  6. System.out.println(bitSet.cardinality());//着色位的个数
  7. System.out.println(bitSet.get(hashcode));//检测存在性
  8. 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. int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;
  5. bitSet.set(hashcode2);
  6. 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. String url = "http://baidu.com/a";
  4. int hashcode1 = url.hashCode() & 0x7FFFFFFF;
  5. bitSet1.set(hashcode1);
  6. int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;
  7. bitSet2.set(hashcode2);
  8. 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));

【转载】BitSet相关推荐

  1. C++ bitset 用法(转载)

    转载自该出处 C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间. 下面是具体用法 构造函数 bitset常用构造函数有四 ...

  2. 算法复习——bitset(bzoj3687简单题)

    题目: Description 小呆开始研究集合论了,他提出了关于一个数集四个问题: 1.子集的异或和的算术和. 2.子集的异或和的异或和. 3.子集的算术和的算术和. 4.子集的算术和的异或和.   ...

  3. bitset类型, 标准库类型

    C++ primer 17.2 bitset类型, 标准库类型 1 使得位运算更容易实现, 并且能够处理超过最长整型大小的位集合. bitset定义在bitset中 定义和初始化bitset 1 bi ...

  4. 2016多校赛2 A 数学推公式 E 极角排序,组合数(待补) L dp+bitset优化

    2016 Multi-University Training Contest 2 A - Acperience 题意:给出w[],求S((w[i]-aB[i])^2)的最小值(B[i]为1或-1). ...

  5. 【JZOJ5064】【GDOI2017第二轮模拟day2】友好城市 Kosarajo算法+bitset+ST表+分块

    题面 在Byteland 一共有n 座城市,编号依次为1 到n,这些城市之间通过m 条单向公路连接. 对于两座不同的城市a 和b,如果a 能通过这些单向道路直接或间接到达b,且b 也能如此到达a,那么 ...

  6. 【拓扑排序】【bitset】Gym - 101128A - Promotions

    给你一张DAG,若选择u点,则必须先选择所有能到达其的点.问你在选择A个点的情况下,哪些点必选:选择B个点的情况下,哪些点必选:选择B个点的情况下,哪些点一定不选. 选择A个点的情况,必选的点是那些其 ...

  7. Codeforces Gym101246C:Explode 'Em All(DP + bitset)

    http://codeforces.com/gym/101246/problem/C 题意:给出一个n*m的图,"*"表示这个地方需要炸掉,炸弹可以如果丢在(i,j)位置的话,那么 ...

  8. BitSet 数字排序

    2019独角兽企业重金招聘Python工程师标准>>> BitSet 数字排序 博客分类: java package tree2; import java.util.BitSet;p ...

  9. lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行...

    如何联合索引查询? 所以给定查询过滤条件 age=18 的过程就是先从term index找到18在term dictionary的大概位置,然后再从term dictionary里精确地找到18这个 ...

  10. BZOJ 4810 [Ynoi2017]由乃的玉米田 ——Bitset 莫队算法

    加法和减法的操作都能想到Bitset. 然后发现乘法比较难办,反正复杂度已经是$O(n\log{n})$了 枚举因数也不能更差了,直接枚举就好了. #include <map> #incl ...

最新文章

  1. 北大博士网恋被骗7400RMB,聊天记录惨遭曝光!
  2. python函数+定义+调用+多返回值+匿名函数+lambda+高级函数(reduce、map、filter)
  3. Oulipo(kmp算法)
  4. 解决 No toolchains found in the NDK toolchains folder for ABI with prefix: arm-linux-androideabi 的问题
  5. TypeError: atlas.getSpriteFrame is not a function
  6. 命令界面:使用Java中的动态API处理Redis
  7. ASP.NET开发安全问题
  8. Github | ICCV2019论文1000余篇更新!附下载链接
  9. oracle ora 01152,ORA-01152 故障解决
  10. ggplot2作图详解:主题(theme)设置
  11. 最新Maven安装教程(详细)
  12. smartsvn 忽略文件夹_Smart SVN-使用Smart SVN 管理项目代码文件(在windows上)
  13. 74ls20设计半加器_实验二——组合逻辑电路的设计与测试.ppt
  14. 蔽月山房---作者,王阳明
  15. GB/T 18487电动汽车充电领域国家标准解析 篇一(充电术语和定义)
  16. AI 写代码来了 - github 的 AI 写代码插件 copilot 发布
  17. vmware安装了vmware tools还是无法复制文件与文本(ubuntu16.04)
  18. 计算机音乐设备简单配置,计算机音乐与作曲基础(高等学校艺术类专业计算机规划教材)...
  19. 网页兼容性调好了么?
  20. powershell牛逼啊.

热门文章

  1. uva 10252 - Common Permutation 字符串水题
  2. RHEL 5.1 下面安装tftp服务
  3. .NET :在Visual Studio的不同Tab之间切换
  4. 学习vim: 配置python相关插件
  5. Java Design Pattern(Factory,Singleton,Prototype,Proxy)
  6. 利用bootstrap的modal组件自定义alert,confirm和modal对话框
  7. linux下 mysql 学习(一)
  8. ES权威指南[官方文档学习笔记]-12 more complicated searches
  9. Ubuntu 12.04(所有ubuntu发行版都适用)sudo免输入密码
  10. 基于 pureXML 技术的数据库表结构扩展