JAVA中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型数字(4个字节)就可以保存64个数字的“存在性”状态(无碰撞冲突时,即true、false状态)。比如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上进行2次“着色”,这样冲突性更小。但会消耗双倍的内存
其实我们能够看出,这种方式降低了误判的概率。但是如果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));
5、内存消耗
据上所述,BitSet可以有效的降低内存的使用量,但是它的内存使用量是有内部long数组的大小决定,所以在创建BitSet时指定的值域非常重要,过大的值域将会导致OOM(比如指定Long.MAX_VALUE),在一个BitMap上存储Integer.MAX_VALUE个“着色”(注意,BitSet只能对正数操作),大概消耗128M内存。
相关推荐
这篇博文主要探讨了如何在多线程环境下正确地使用Java的BitSet。 首先,我们要理解BitSet的基本原理。BitSet内部以long数组存储位,每一位代表一个布尔值,0表示false,1表示true。由于long的基本操作(如按位或、...
1. 每个BitSet中的位都有一个true或false的值。 2. 位的索引是从0开始的非负整数。 3. 可以通过索引检查、设置或清除个别位。 4. 通过逻辑与(AND)、逻辑或(OR)和逻辑异或(XOR)操作来修改另一个BitSet的内容。 ...
Java BitSet 使用场景和代码示例 Java BitSet 是 Java 中的一个重要类,它实现了一个按需增长的位向量。BitSet 的每一个组件都有一个 boolean 值,用非负的整数将 BitSet 的位编入索引。可以对每个编入索引的位进行...
RoaringBitmap, 在Java中,一个更好的压缩 bitset RoaringBitmap Bitsets,也称为位图,通常用作快速数据结构。 不幸的是,他们可以使用太多的内存。 为了补偿,我们经常使用压缩位图。咆哮位图是压缩位图,它比传统...
java bitset 高级数据结构 源码解析 适合 0-3 年开发人员,进阶、面试必备知识!
在Java8之前,接口中只能包含抽象方法。那么这有什么样弊端呢?比如,想再Collection接口中添加一个spliterator抽象方法,那么也就意味着之前所有实现Collection接口的实现类,都要重新实现spliterator这个方法才行...
java基础之BitSet - 副本
基于JDK1.8的BitSet 源码分析, 描述了实现的原理 个方法的含义 虽然没有写出实际的测试代码 但是只要是细度了我的这个分析 在使用的时候就不是问题了
那么words[wordIndex]值将是1111,表示整数0、1、2、3、4在bitset中存在。 在查询时,我们可以使用bitset索引来快速查询数据。例如,如果我们要查询“北京市18岁的女生”,那么我们可以使用bitset索引来快速查询...
Java编程中的HashSet和BitSet详解 HashSet和BitSet是Java编程中两个常用的集合类,它们都可以用来存储大量的数据,但它们之间有着明显的差异。那么,为什么Apache Commons作者选择使用BitSet代替HashSet呢?在本文...
bitset源码Java源码分析 基础集合列表 ArrayList (done) Vector (done) LinkedList (done) Stack (done) ReferenceQueue (done) ArrayDeque (done) Set HashSet (done) TreeSet (done) LinkedHashSet (done) BitSet ...
bitset源码Java 这是 Java Bitset 类的字对齐压缩变体。 我们提供 64 位和 32 位类似 RLE 的压缩方案。 它可用于实现位图索引。 它所依赖的 EWAH 格式用于运行 GitHub 的 git 实现。 字对齐压缩的目标不是实现最佳...
java bitset 源码 最后更新于20180424 (Toc generated by ) 数据结构 队列 非阻塞队列:ConcurrentLinkedQueue(无界线程安全),采用CAS机制(compareAndSwapObject原子操作)。 阻塞队列:ArrayBlockingQueue(有界...
Java util包使用详解 Java util包是Java语言中一个实用的工具类库,提供了许多有用的方法和数据结构。下面将逐一介绍其中几个重要的类。 日期类Date Java中的日期类封装了有关日期和时间的信息,用户可以通过调用...
java bitset源码 目前进度(171/237) LeetCode做题笔记 Add two numbers:给定一个数集合和一个数,已知集合中有两个数的和是给定数,求这两个加数的index 方法1:暴力,n^2时间复杂度,不推荐 方法2:快速排序nlogn...
在标题提及的 "javabitset源码-montysolr:Solr天体物理数据系统" 中,我们可以推测这个项目可能是在Solr(一个流行的全文搜索引擎)中使用BitSet来处理天体物理数据。下面我们将深入探讨Java BitSet以及它如何应用于...
Java中的锁和同步类 公平锁 & 非公平锁 悲观锁 乐观锁 & CAS ABA 问题 CopyOnWrite容器 RingBuffer 可重入锁 & 不可重入锁 互斥锁 & 共享锁 死锁 操作系统 计算机原理 CPU 多级缓存 进程 线程 协程 Linux 设计模式 ...
bitset 源码 all-kinds-book 主要包含 java 大数据 数据仓库 数据分析 第三方组件 面试题 数据结构与算法 设计模式 软件设计 等文档 ,可以访问我们的官网查看更多内容 [人在地上跑 牛在天上飞](#人在地上跑 牛在...
2.在 Java 中,String, BitSet, Date, 和 File 等类都对 equals 方法进行了重写,对两个 String 对象而言,值相等意味着它们包含同样的字符序列。 五、Java 中的 hashCode 和 equals 方法 1.在 Java 中,hashCode ...
在Java中,我们可以使用java.awt.image包中的类,如BufferedImage,来创建和操作数字图像。 接着,我们将介绍几种基本的图像处理操作,它们在Java中是如何实现的: 1. 图像读取与显示:使用Java的ImageIO类可以...