`
QING____
  • 浏览: 2253448 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JAVA中BitSet使用

    博客分类:
  • JAVA
 
阅读更多

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的多线程并发的探索

    这篇博文主要探讨了如何在多线程环境下正确地使用Java的BitSet。 首先,我们要理解BitSet的基本原理。BitSet内部以long数组存储位,每一位代表一个布尔值,0表示false,1表示true。由于long的基本操作(如按位或、...

    java 原生包 BitSet 源码

    1. 每个BitSet中的位都有一个true或false的值。 2. 位的索引是从0开始的非负整数。 3. 可以通过索引检查、设置或清除个别位。 4. 通过逻辑与(AND)、逻辑或(OR)和逻辑异或(XOR)操作来修改另一个BitSet的内容。 ...

    浅谈Java BitSet使用场景和代码示例

    Java BitSet 使用场景和代码示例 Java BitSet 是 Java 中的一个重要类,它实现了一个按需增长的位向量。BitSet 的每一个组件都有一个 boolean 值,用非负的整数将 BitSet 的位编入索引。可以对每个编入索引的位进行...

    RoaringBitmap, 在Java中,一个更好的压缩 bitset.zip

    RoaringBitmap, 在Java中,一个更好的压缩 bitset RoaringBitmap Bitsets,也称为位图,通常用作快速数据结构。 不幸的是,他们可以使用太多的内存。 为了补偿,我们经常使用压缩位图。咆哮位图是压缩位图,它比传统...

    java bitset 源码解析.rtf

    java bitset 高级数据结构 源码解析 适合 0-3 年开发人员,进阶、面试必备知识!

    javabitset源码-Java8-learning:学习Java8新特性

    在Java8之前,接口中只能包含抽象方法。那么这有什么样弊端呢?比如,想再Collection接口中添加一个spliterator抽象方法,那么也就意味着之前所有实现Collection接口的实现类,都要重新实现spliterator这个方法才行...

    java基础之BitSet - 副本.md

    java基础之BitSet - 副本

    BitSet 源码分析.txt

    基于JDK1.8的BitSet 源码分析, 描述了实现的原理 个方法的含义 虽然没有写出实际的测试代码 但是只要是细度了我的这个分析 在使用的时候就不是问题了

    使用bitset实现毫秒级查询(实例讲解)

    那么words[wordIndex]值将是1111,表示整数0、1、2、3、4在bitset中存在。 在查询时,我们可以使用bitset索引来快速查询数据。例如,如果我们要查询“北京市18岁的女生”,那么我们可以使用bitset索引来快速查询...

    Java编程中的HashSet和BitSet详解

    Java编程中的HashSet和BitSet详解 HashSet和BitSet是Java编程中两个常用的集合类,它们都可以用来存储大量的数据,但它们之间有着明显的差异。那么,为什么Apache Commons作者选择使用BitSet代替HashSet呢?在本文...

    javabitset源码-JerrySoundCode:杰瑞声码

    bitset源码Java源码分析 基础集合列表 ArrayList (done) Vector (done) LinkedList (done) Stack (done) ReferenceQueue (done) ArrayDeque (done) Set HashSet (done) TreeSet (done) LinkedHashSet (done) BitSet ...

    javabitset源码-javaewah:JavaBitSet类的压缩替代品

    bitset源码Java 这是 Java Bitset 类的字对齐压缩变体。 我们提供 64 位和 32 位类似 RLE 的压缩方案。 它可用于实现位图索引。 它所依赖的 EWAH 格式用于运行 GitHub 的 git 实现。 字对齐压缩的目标不是实现最佳...

    javabitset源码-java_master:后端架构师技术图谱

    java bitset 源码 最后更新于20180424 (Toc generated by ) 数据结构 队列 非阻塞队列:ConcurrentLinkedQueue(无界线程安全),采用CAS机制(compareAndSwapObject原子操作)。 阻塞队列:ArrayBlockingQueue(有界...

    java util包使用详解

    Java util包使用详解 Java util包是Java语言中一个实用的工具类库,提供了许多有用的方法和数据结构。下面将逐一介绍其中几个重要的类。 日期类Date Java中的日期类封装了有关日期和时间的信息,用户可以通过调用...

    javabitset源码-myleetcode:所有LeetCode问题的记录

    java bitset源码 目前进度(171/237) LeetCode做题笔记 Add two numbers:给定一个数集合和一个数,已知集合中有两个数的和是给定数,求这两个加数的index 方法1:暴力,n^2时间复杂度,不推荐 方法2:快速排序nlogn...

    javabitset源码-montysolr:Solr天体物理数据系统

    在标题提及的 "javabitset源码-montysolr:Solr天体物理数据系统" 中,我们可以推测这个项目可能是在Solr(一个流行的全文搜索引擎)中使用BitSet来处理天体物理数据。下面我们将深入探讨Java BitSet以及它如何应用于...

    javabitset源码-Study:学习

    Java中的锁和同步类 公平锁 & 非公平锁 悲观锁 乐观锁 & CAS ABA 问题 CopyOnWrite容器 RingBuffer 可重入锁 & 不可重入锁 互斥锁 & 共享锁 死锁 操作系统 计算机原理 CPU 多级缓存 进程 线程 协程 Linux 设计模式 ...

    javabitset源码-all-kinds-book:java大数据sparkflinkredishivehbasekafka面试题数据结构

    bitset 源码 all-kinds-book 主要包含 java 大数据 数据仓库 数据分析 第三方组件 面试题 数据结构与算法 设计模式 软件设计 等文档 ,可以访问我们的官网查看更多内容 [人在地上跑 牛在天上飞](#人在地上跑 牛在...

    java基础面试题.doc

    2.在 Java 中,String, BitSet, Date, 和 File 等类都对 equals 方法进行了重写,对两个 String 对象而言,值相等意味着它们包含同样的字符序列。 五、Java 中的 hashCode 和 equals 方法 1.在 Java 中,hashCode ...

    数字图像处理 java 语言描述

    在Java中,我们可以使用java.awt.image包中的类,如BufferedImage,来创建和操作数字图像。 接着,我们将介绍几种基本的图像处理操作,它们在Java中是如何实现的: 1. 图像读取与显示:使用Java的ImageIO类可以...

Global site tag (gtag.js) - Google Analytics