java BitSet原理解析

java基础】BitSet基本说明和使用 Java平台的BitSet类用于存储一个位序列(它不是数学上的集,如果称为位向量或位数组可能更为合适)。如果需要高效地存储位序列(例如,标志),就可以使用位集。由于位集将位包装在字节里,所以使用位集要比使用Boolean对象的ArrayList高效得多。说人话就是使用位来存储boolean信息,0表示假,1表示真。BitSet由于使用bit位来存储信息,所以占用空间会比boolean数组使用空间更小,并且带有去重功能。该数据结构的一个应用就是10亿个手机号码去重(面试造火箭) 阅读详情

       学习了Java BitSet之后,写下这篇博文记录一下BetSet的原理及使用场景。

一、BitSet原理

       BitSet,通过这个名字字面意思(位集合),就可以知道,它的使用应该与位(Bit)有关。事实也确实如此,它(BitSet)用于标志一个“东西”是否存在于这个“东西”的一个集合里面。这句话很拗口,举实例来说明,比如:一个字符是否存在一个字符串中。一个数字是否存在一个数字集合中等等,也就是可以用BitSet来进行大量数据的统计和筛选。

       那么,BitSet是如何标志的呢?考虑一下这样的一个方案,假如有由20个不同的数字组成的数组,需要确定这20个数字分别是否等于某一个数字A。那么可以定义一个长度是20的boolean数组,然后遍历数字数组,判断数组元素是不是等于这个数字A,如果不等于,则将boolean数组对应下标的元素设置为false,如果等于,则将对应下标的元素设置成true。这是在数组(集合)元素少的情况,假如,我需要确定的是在一千万或者一亿个数字分别是否等于数字A呢?

         这个时候如果你使用数组的话就会有一个内存销量的问题,因为boolean变量不是true就是false,也就是不是1就是0,在内存中只占用1位就可以了,但是java没有明确指出boolean类型占用内存的大小,有可能是1位,有可能是1个字节,有可能是4个字节。那么如果不是1位的情况,那么这么大数量的boolean数组,占用的内存就很大了,造成了浪费。

        BitSet的使用就可以解决这样的问题。在BitSet中,它是使用long类型来标志的。一个long变量长度是64位,每个位的值可以是0或者1,这样一个long类型变量最多就可以对64个数字(或者其他数据)进行标志。在BitSet中,维护的是一个long类型的数组。这样的话,针对数量很大的集合,就可以使用多个long元素来进行标志。同时这也意味着BitSet最少是一个long类型的长度。对于集合长度少于不大的,使用BitSet优势就没有那么明显了。

        下面从“求1024里面的素数”方法来分析一下BitSet的使用。

//求1024之前的素数
private static void computerPrime(){
        BitSet sieve = new BitSet(1024);
        int size = sieve.size();
        System.out.println(size);
        for (int i = 2; i < size; i++) {
            sieve.set(i);
        }
        System.out.println(size);
        int finalBit = (int) Math.sqrt(size);

        for (int i = 2; i < finalBit; i++) {
            if (sieve.get(i)) {
                for (int j = 2*i; j < size; j+=i) {
                    sieve.clear(j);
                }
            }
        }
        int counter = 0;
        for (int i = 1; i < size; i++) {
            if (sieve.get(i)){
                System.out.printf("%5d",i);
                if (++counter % 15 == 0){
                    System.out.println();
                }
            }
        }
        System.out.println();
}

       首先创建了一个BitSet,通过查看BitSet的源码,可以发现BitSet对外提供两个构造函数。第一个默认构造函数初始化words数组的时候,给initWords方法传入的数值是64(BITS_PER_WORD是1左移6位之后的值,64)。而在initWords方法中传入wordIndex的值是63,经过在wordIndex方法中右移6位之后变成0,又加1,所以使用默认构造函数情况下,words的长度是1。第二个构造方法,需要传入指定的长度,这个长度值的作用是用来确定BitSet需要多少位进行数据的统计、筛选。如上面的computerPrime方法,求1024之前的素数个数,要对着1024个数字来进行标志需要1024位。那么1024位是多少个long呢?那就是1024/64=16,这是在刚好能被64整除的情况。正确的事从initWords和wordIndex中得到的是( ((1024-1) >> 6)+1),这样可以确保在不能被64整除的情况下,得到正确的words数组长度。

private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;//64

private long[] words;//long数组
  
private transient int wordsInUse = 0;//long数组中有几个元素被使用

public BitSet() {//默认构造函数
        initWords(BITS_PER_WORD);
        sizeIsSticky = false;
}

public BitSet(int nbits) {//传参构造函数
        // nbits can't be negative; size 0 is OK
        if (nbits < 0)
            throw new NegativeArraySizeException("nbits < 0: " + nbits);

        initWords(nbits);
        sizeIsSticky = true;
}

private void initWords(int nbits) {//初始化数组
        words = new long[wordIndex(nbits-1) + 1];
}

private static int wordIndex(int bitIndex) {
        return bitIndex >> ADDRESS_BITS_PER_WORD;
}

      再看BitSet的set方法。首先判断确保传入的值要大于0,然后通过wordIndex方法,将传入的值右移6位,这个方法是用于查找传入的值的标志应该放在words数组的那个元素的索引(index)。比如如果传入的是0-63,这时候,右移6位之后,得到0,就会把这些值的标志放在words数组的第0个位置的元素,如果是64-127,右移6位之后,得到1,就会放在word的第一个元素上。expandTo方法是设置words数组用到了哪一个位置的元素,如果words数组长度不够,那就调用ensureCapacity方法延长words的长度。words[wordIndex] |= (1L << bitIndex);这一句是将words数组相应位置上的元素的位设置成相应的标志。

public void set(int bitIndex) {//set方法
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        int wordIndex = wordIndex(bitIndex);
        expandTo(wordIndex);

        words[wordIndex] |= (1L << bitIndex); // Restores invariants

        checkInvariants();
}

private static int wordIndex(int bitIndex) {//传入的值右移6位
        return bitIndex >> ADDRESS_BITS_PER_WORD;
}

private void expandTo(int wordIndex) {
//判断是不是用完了一个long变量的长度(64位),是的话,就从下一个变量开始设置
        int wordsRequired = wordIndex+1;
        if (wordsInUse < wordsRequired) {
            ensureCapacity(wordsRequired);
            wordsInUse = wordsRequired;
        }
}

//如果words数组长度不够,则延长words数组的长度
private void ensureCapacity(int wordsRequired) {
        if (words.length < wordsRequired) {
            // Allocate larger of doubled size or required size
            int request = Math.max(2 * words.length, wordsRequired);
            words = Arrays.copyOf(words, request);
            sizeIsSticky = false;
        }
}

       再看BitSet的get方法。首先判断确保传入的值要大于0,checkInvariants方法是用于检测各个变量是不是正常。然后通过wordIndex方法,获取bitIndex这个值的标志在words数组中的哪个位置(index)的元素上,最后返回bitIndex这个值的标志。

public boolean get(int bitIndex) {//get方法
        if (bitIndex < 0)
            throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        checkInvariants();

        int wordIndex = wordIndex(bitIndex);
        return (wordIndex < wordsInUse)
            && ((words[wordIndex] & (1L << bitIndex)) != 0);
}

private void checkInvariants() {//判断各个变量是否正常
        assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
        assert(wordsInUse >= 0 && wordsInUse <= words.length);
        assert(wordsInUse == words.length || words[wordsInUse] == 0);
}

      BitSet内部使用long而不是用其他类型来进行标志的存储是因为:BitSet内部提供了位运算(and、or),这两个操作需要对words中的位逐位运算。long是基本数据类型里面占用位数最多的数据类型,这样可以使得同等情况下,words数组长度最小,也就是循环次数最少(比如,初始化Bitset需要设置标志位长度为1024时,long类型,words的长度是16。如果words使用int类型,words长度将会是32)。循环次数越多,会导致性能下降。所以使用的是long。

      使用BitSet排序:

private static void sort(){
        int[] a = {12,54,63,10,8,5,6,3,5,4,7,456,8411,21,1};

        BitSet bitSet = new BitSet();
        for (int anA : a) {
            bitSet.set(anA);
        }
        int k = 0;
        int len = bitSet.cardinality();
        int b[] = new int[len];
        for (int i = bitSet.nextSetBit(0); i >=0 ; i = bitSet.nextSetBit(i+1)) {
            b[k++] = i;
        }
        for (int aB : b) {
            System.out.println(aB);
        }
}

 

JAVA8中的BitSet 在看Arraylist码的时候发现在 removeIf方法中使用了BitSet类。 确实不太好理解,看了半天才看懂。 BitSet(位图) 官方解释如下 This class implements a vector of bits that grows as needed. Each component of the bit set has a boolean value. The bits o... 阅读详情

相关推荐

手把手教你用AirSim和Unreal4在Ubuntu上创建自定义无人机仿真场景

本指南详细介绍了如何在Ubuntu系统上使用AirSim和Unreal Engine 4搭建高精度无人机仿真环境。从硬件配置、驱动安装到Unreal Engine编译和AirSim集成,手把手教你创建自定义场景,适用于算法验证和开发测试。

y7z8a的博客 231

JavaBitSet

JavaBitSet 就是“位图”数据结构,根据“位图”的语义,数据的存在性可以使用 bit 位上的1或0来表示;一个 bit 具有2个值:0和1,正好可以用来表示 false 和 true。对于判断“数据是否存在”的场景,我们通常使用 HashMap 来存储,不过 hashmap 这个数据结构 Key 和 Value 的保存需要消耗较多的内存,不适合保存较多的数据,即大数据场景

zhangxin09的专栏 8277

New API 大模型网关保姆级教程:Docker Compose 部署 + MiniMax 接入 Claude 实战

New API 是一个开源的 AI 基座平台,提供统一的 API 接口接入 30+主流 AI 服务(如 OpenAI、Claude 等)。核心功能包括智能路由、精细计费、安全管控和多租户支持,采用 AGPLv3 协议开源。支持 Docker Compose 快速部署,通过修改配置文件即可连接不同数据库,适合从开发到生产环境的不同规模应用。部署时需注意修改默认密码等安全配置。

甘蓝的专栏 1453

Java 数据结构——BitSet

位图有很多使用场景,例如过滤或者数据缓存的场景,使用的好的可以很好的提高我们程序的性能由于Java中没有bit 这种数据类型,所以javaBitSet使用了long 代替了bit。

2401_89221867的博客 2026

javaBitSet

javaBitSet

听海边涛声 1298

Java基础---BigSet

BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,最终内部是由N个long来存储,这些针对操作都是透明的。可以看到,如果指定的bit不存在的话,返回的是false,即没有设置。wordsInUse 是检查当前的long数组中,实际使用的long的个数,即long[wordsInUse-1]是当前最后一个存储有有效bit的long。

学英语的程序员 1302

java bitset clean方法_BitSet实现原理及源码解

BitSet的结构原理BitSet, 是Java对位集合抽象出的一种数据结构。它的内部维护了一个long数组,数组里的每一个元素用64位的二进制来表示,所以每一位只用来存储0,1值。BitSet只知道给定的数字是否存在,并不能还数字本身; 所以它一般用来做精确去重,比如布隆过滤器也是基于位数组来实现的。它的数据结构可以用下图来表示:BitSet的words数据结构上图整体表示的是一个long型的...

weixin_30667831的博客 339

java.util.BitSet.or(BitSet set) 

java.util.BitSet.or(BitSet set) 源代码: public void or(BitSet set) { if (this == set) return; int wordsInCommon = Math.min(wordsInUse, set.wordsInUse); if (wordsInUse < set.wordsInUse) { ensureCapac.

zhm1002的博客 261

数据结构-BitSet的实现原理-java.util.BitSet

今天看es看到,es使用bitset来标记一个文档是否匹配,于是就看了下java.util中的BitSet实现原理,记录一下 该数据结构能够实现什么 void set(int id):设置id存在 boolean get(int id): 查看id是否存在 容易想到用bit位的1或者0来代表, 那么一个long的长度8字节(64位),能存储64个位置的状态,对于id来说需要多少个long才能存储所...

weixin_37512224的博客 397

Java BitSet使用场景和示例

阅读目录一、什么是BitSet?二、Java BitSet实现原理三、使用场景四、参考资料 回到顶部一、什么是BitSet?   注:以下内容来自JDK API:   BitSet类实现了一个按需增长的位向量。位Set的每一个组件都有一个boolean值。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet修改另一个 BitSet的内容。    默认情况下,set 中所有位的初始值都是false。

灼烧的疯狂 488

bitset java 源码_【JAVABitSet的源码研究

这几天看Bloom Filter,因为在java中,并不能像C/C++一样直接操纵bit级别的数据,所以只能另想办法替代:1)使用整数数组来替代;2)使用BitSetBitSet实际是由“二进制位”构成的一个Vector。如果希望高效率地保存大量“开-关”信息,就应使用BitSet。它只有从尺寸的角度看才有意义;如果希望的高效率的访问,那么它的速度会比使用一些固有类型的数组慢一些。BitSet的...

weixin_39639174的博客 133

bitset详解

bitset详解 JavaBitSet使用一个Long(一共64位)的数组中的没一位(bit)是否为1来表示当前Index的数存在不。但是BitSet又是如何实现的呢?其实只需要理解其中的两个方法: set get set 先看源代码: public void set(int bitIndex) { if (bitIndex < 0) throw new Ind...

u011624157的专栏 861

javaBitSet详解

该类在javajava.util包中。 BitSet类的原理主要是利用long型有64个bit,每个bit存储一个数值,这样一个long型数字就能存储64个数字,进而节省了空间。接下来我们开始讲解该类的代码。 我们先看一下该类的成员变量: /* * BitSets are packed into arrays of "words." Currently a word is ...

weixin_39935887的博客 3923

javaBitSet系列之源码解

java中有8种基本数据类型,但是没有关于bit的数据类型,而BitSet可以看成是bit的基本类型。 理由: 首先它只存放0和1,进行的操作也是常见的位运算(and,or,xor)。 重要属性: /*一个long可以表示64个bit位,这里是用位移来代替乘除64的操作*/ private final static int ADDRESS_BITS_PER_WORD = 6; /*保存b...

weixin_39424099的博客 390

Java BitSet理解与使用

https://blog.csdn.net/drdongshiye/article/details/90740695 一,概括 BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,一次扩充64位,最终内部是由N个long来存储。 默认情况下,BitSet的所有位都是false即0。 在没有外部同步的情况下,多个线程操作一个BitSet是不安全的。

yygr的博客 1466

BitSet的实现原理

1.BitSet介绍 BitsetJava中的一种数据结构。Bitset中主要存储的是二进制位,做的也都是位运算,每一位只用来存储0,1值,主要用于对数据的标记。 Bitset的基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用的时候可以根据某一个位是否为0表示此数是否出现过。JDK中的BitSet集合对是布隆过滤器中经常使用的数据结构Bitmap的相对简单的实现...

forever-and-ever的博客 2万+

JavaBitSet的使用及详解

BitSet是一个非常强大的工具,适用于需要高效管理和操作大量布尔值的场景。通过使用BitSet,可以显著减少内存使用,并提高程序的执行效率.它在数据库索引、权限管理、状态标记等方面有着广泛的应用。版权声明:本博客内容为创,转载请保留文链接及作者信息。参考文章学会使用BitSet浅谈Java BitSet使用场景和代码示例。

ning的博客 1679

java中的BitSet使用详解

Java中的BitSet类是一个高效的位操作工具类,用于表示可动态扩展的位向量。它具有动态扩容、空间效率高(每位仅占1bit)等特点,但不支持线程安全。基础操作包括设置/清除位、获取状态和翻转位,支持各种位运算(与/或/异或等)。BitSet适用于海量数据标记、集合运算、布隆过滤器等场景,相比boolean数组显著节省内存(1000元素仅需125字节)。使用时需注意索引越界、自动扩容等问题,多线程环境可考虑ConcurrentBitSet。通过合理使用BitSet,能在空间效率和性能间取得良好平衡。

技术人集结地 1229

Java基础:JAVABitSet使用详解

适用场景 整数,无重复 Bitset 基础 Bitset,也就是位图,由于可以用非常紧凑的格式来表示给定范围的连续数据而经常出现在各种算法设计中。上面的图来自c++库中bitset的一张图。 基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用用的时候既可根据某一个是否为0表示此数是否出现过。 一个1G的空间,有 8102410241024=8.5810^9bit,也就...

TuT 8359

中国城市建设数据库(2002-2023年)中国城市建设统计年鉴指标数据

中国城市建设数据库,是根据历年《中国城市建设统计年鉴》数据整理。包括城市人口、建设、财政、投资、供水、节水、燃气、供热、交通、环境、卫生、绿化等13个部分。与中国城市数据库不同,中国城市建设数据库覆盖范围更广,包括全国693个城市。共计331个指标。内含始数据、线性插值、回归填补版本。数据名称:中国城市建设数据库数据年份:2002-2023数据范围:地级市数据格式:Excel数据来源:《中国城市建设统计年鉴》

上一篇: Java 程序初始化过程
下一篇: Java之反射机制
林序
博客等级 码龄12年 12粉丝 47原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值