本文转载自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],依次轮推,这样就避免了位运算导致的冲突。
- |------------|----------|----------|----------|----------|
- |
- | 数字范围 [0,63] [64,127] [128,191] ... |
- |------------|----------|----------|----------|----------|
- |
- |long数组索引 0 1 2 ... |
- |------------|----------|----------|----------|----------|
3) bitSet内部的long[]数组是基于向量的,即随着set的最大数字而动态扩展。数组的最大长度计算:
- (maxValue - 1) >> 6 + 1
4) BitSet中set方法伪代码:
- public void set(int number) {
- int index = number >> 6;//找到number需要映射的数组的index。
- if(index + 1 > length) {
- ensureCapacity(index + 1);//重新扩展long[]数组
- }
- long[index] |= (1L << number);//冲突解决
- }
2、使用BitSet:本例中使用bitSet做String字符串的存在性校验。
- BitSet bitSet = new BitSet(Integer.MAX_VALUE);//hashcode的值域
- //0x7FFFFFFF
- String url = "http://baidu.com/a";
- int hashcode = url.hashCode() & 0x7FFFFFFF;
- bitSet.set(hashcode);
- System.out.println(bitSet.cardinality());//着色位的个数
- System.out.println(bitSet.get(hashcode));//检测存在性
- 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时,才判定成功。
- String url = "http://baidu.com/a";
- int hashcode1 = url.hashCode() & 0x7FFFFFFF;
- bitSet.set(hashcode1);
- int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;
- bitSet.set(hashcode2);
- 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的值域,避免内存过度消耗。
- BitSet bitSet1 = new BitSet(Integer.MAX_VALUE);//127M
- BitSet bitSet2 = new BitSet(Integer.MAX_VALUE);
- String url = "http://baidu.com/a";
- int hashcode1 = url.hashCode() & 0x7FFFFFFF;
- bitSet1.set(hashcode1);
- int hashcode2 = (url + "-seed-").hashCode() & 0x7FFFFFFF;
- bitSet2.set(hashcode2);
- 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。如下为代码样例:
- Charset charset = Charset.forName("utf-8");
- BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(charset),2<<21);//指定bloomFilter的容量
- String url = "www.baidu.com/a";
- bloomFilter.put(url);
- System.out.println(bloomFilter.mightContain(url));
相关推荐
### Bitset用法详解 在C++编程语言中,`bitset`是一个非常有用的类模板,它可以帮助程序员高效地处理二进制数据。`bitset`的主要功能是存储位序列,并提供了丰富的成员函数来对这些位进行操作。下面我们将详细介绍`...
C语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC语言头文件 BITSETC...
在C++的STL中实现由一个bitset类模板,其用法如下: std::bitset<64> bs; 也就是说,这个bs只能支持64位以内的位存储和操作;bs一旦定义就不能动态增长了。本资源附件中实现了一个动态Bitset,和标准bitset兼容。 /*...
Java的BitSet是一个实用工具类,它提供了位操作的功能,常用于存储一组可变的布尔值。在多线程并发环境中,对BitSet的操作需要特别注意,因为位操作本身是原子性的,但BitSet的大部分方法并不是线程安全的。这篇博文...
`C++ bitset` 是一个内置的类型,用于在内存中高效存储和操作位序列。在C++标准库中,`<bitset>` 头文件提供了`std::bitset` 类模板,它允许我们创建一个固定大小的位集合,类似于二进制数。然而,题目中的描述表明...
在Go编程语言中,`bitset`是一个非常实用的包,用于高效地处理位集(也称为位向量或位数组)。位集是一种特殊的数据结构,它使用二进制位来表示一系列布尔值,通常用于存储有限集合的成员资格。Go的`bitset`包实现了...
文档模仿STL库的BITSET写的一个bitset,但是和STL不同的是这个类是一个可以动态扩展的,使用方法和STL的类似,可以参考STL使用
C++中的`bitset`是一个非常实用的容器,它允许我们以数组的形式操作位,从而方便地处理位级别的逻辑运算。`bitset`是C++标准库的一部分,属于STL(Standard Template Library),它提供了高效且易用的方法来管理一组...
Java中BitSet类是Java集合框架的一部分,它是一种用于处理位操作的高级数据结构。BitSet可以看作是一个只存储布尔值的数组,但相比于原始的布尔数组,BitSet更加内存高效,因为它以64位的块(word)来存储多个布尔值...
This bitset type is a thin wrapper round a dm_array of 64bit words.This bitset type is a thin wrapper round a dm_array of 64bit words.
基于JDK1.8的BitSet 源码分析, 描述了实现的原理 个方法的含义 虽然没有写出实际的测试代码 但是只要是细度了我的这个分析 在使用的时候就不是问题了
C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,它的每一个元素只能是0或1,每个元素仅用1bit空间。 下面是具体用法 构造函数 bitset常用构造函数有四种,如下 bitset<4> bitset1; //无参构造,...
Java编程中的HashSet和BitSet详解 HashSet和BitSet是Java编程中两个常用的集合类,它们都可以用来存储大量的数据,但它们之间有着明显的差异。那么,为什么Apache Commons作者选择使用BitSet代替HashSet呢?在本文...
认识标准库bitset类型 位是用来保存一组项或者条件的yes/no(1或者0)信息的一种简洁方法,那么位集是二进制位的有序集。C++中标准库提供的bitset类在我们程序中很有效的简化了对于位集的处理。 bitset对象的...
使用C++编写的遗传算法,代码量200行左右,供大家学习研究,互相交流。
Java BitSet 使用场景和代码示例 Java BitSet 是 Java 中的一个重要类,它实现了一个按需增长的位向量。BitSet 的每一个组件都有一个 boolean 值,用非负的整数将 BitSet 的位编入索引。可以对每个编入索引的位进行...
《前端项目:深入理解bitset.js库》 在前端开发中,高效的数据结构和算法是提升应用性能的关键。其中,bitset是一种特殊的数据结构,它在内存中以二进制位的形式存储数据,常用于大规模布尔值的高效管理。本文将...
使用bitset实现毫秒级查询实例讲解 bitset是Java中的一种数据结构,通过使用bitset可以实现毫秒级查询。下面我们将详细讲解如何使用bitset实现毫秒级查询。 bitset的内部实现是long数组,每一个位的默认值为false...
C++ bitset——高端压位卡常题必备STL ———————————————————— 以下内容翻译自cplusplus.com,极大地锻炼了我的英语能力。 bitset存储二进制数位。 bitset就像一个bool类型的数组一样,但是有空间...