Bitset类
一个Bitset类创建一种特殊类型的数组来保存值。BitSet中数组大小会随需要增加。这和位向量比较类似。
Java平台的BitSet用于存放一个位序列,如果要高效的存放一个位序列,就可以使用位集(BitSet)。由于位集将位包装在字节里,所以使用位集比使用Boolean对象的List更加高效和更加节省存储空间。
BitSet是位操作的对象,值只有0或1即false和true,内部维护了一个long数组,初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充,一次扩充64位,最终内部是由N个long来存储。
默认情况下,BitSet的所有位都是false即0。
在没有外部同步的情况下,多个线程操作一个BitSet是不安全的。
一个1GB的空间,有8102410241024 = 8.5810^9bit,也就是1GB的空间可以表示85亿多个数。
- 比如,一个BitSet是怎么存储数字0,2,4,6,8,10,12,14呢?
对应值 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
位 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
BitSet()
第二个方法允许用户指定初始化大小。所有位初始化位0
BitSet(int size)
- void and(BitSet set)
对此目标位 set 和参数位 set 执行逻辑与操作。
- void andNot(BitSet set)
清除此 BitSet 中所有的位,其相应的位在指定的 BitSet 中已设置。
- int cardinality( )
返回此 BitSet 中设置为 true 的位数。
- void clear( )
将此 BitSet 中的所有位设置为 false。
- void clear(int index)
将索引指定处的位设置为 false。
- void clear(int startIndex, int endIndex)
将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 false。
- Object clone( )
复制此 BitSet,生成一个与之相等的新 BitSet。
- boolean equals(Object bitSet)
将此对象与指定的对象进行比较。
- void flip(int index)
将指定索引处的位设置为其当前值的补码。
- void flip(int startIndex, int endIndex)
将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的每个位设置为其当前值的补码。
- boolean get(int index)
返回指定索引处的位值。
- BitSet get(int startIndex, int endIndex)
返回一个新的 BitSet,它由此 BitSet 中从 fromIndex(包括)到 toIndex(不包括)范围内的位组成。
- int hashCode( )
返回此位 set 的哈希码值。
- boolean intersects(BitSet bitSet)
如果指定的 BitSet 中有设置为 true 的位,并且在此 BitSet 中也将其设置为 true,则返回 true。
- boolean isEmpty( )
如果此 BitSet 中没有包含任何设置为 true 的位,则返回 true。
- int length( )
返回此 BitSet 的"逻辑大小":BitSet 中最高设置位的索引加 1。
- int nextClearBit(int startIndex)
返回第一个设置为 false 的位的索引,这发生在指定的起始索引或之后的索引上。
- int nextSetBit(int startIndex)
返回第一个设置为 true 的位的索引,这发生在指定的起始索引或之后的索引上。
- void or(BitSet bitSet)
对此位 set 和位 set 参数执行逻辑或操作。
- void set(int index)
将指定索引处的位设置为 true。
- void set(int index, boolean v)
将指定索引处的位设置为指定的值。
- void set(int startIndex, int endIndex)
将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 true。
- void set(int startIndex, int endIndex, boolean v)
将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为指定的值。
- int size( )
返回此 BitSet 表示位值时实际使用空间的位数。
- String toString( )
返回此位 set 的字符串表示形式。
- void xor(BitSet bitSet)
对此位 set 和位 set 参数执行逻辑异或操作。
package com.java1995;
import java.util.BitSet;
public class BitSetTest {
public static void main(String[] args){
BitSet bits1 = new BitSet(16);
BitSet bits2 = new BitSet(16);
for (int i = 0;i < 16;i++){
if ((i%2)==0)bits1.set(i);
if ((i%5)!=0)bits2.set(i);
}
System.out.println("Initial pattern in bits1:");
System.out.println(bits1);
System.out.println("Initial pattern in bits2:");
System.out.println(bits2);
bits2.and(bits1);
System.out.println("bits2 and bits1:");
System.out.println(bits2);
bits2.or(bits1);
System.out.println("bits2 or bits1:");
System.out.println(bits2);
bits2.xor(bits1);
System.out.println("bits2 xor bits1:");
System.out.println(bits2);
}
}
应用场景
- 统计一组大数据中没有出现过的数
将这组数据映射到BitSet,然后便利BitSet,对应位位0的数表示没有出现过的数据。
- 对大数据进行排序
将数据映射到BitSet,遍历BitSet得到的就是有序数据
- 在内存对大数据进行压缩存储等等
1GB的内容空间可以存储85亿多个数,可以有效实现数据的压缩存储,节省内存开销
为什么BitSet使用long数组做内部存储?
JDK选择long数组作为BitSet的内部存储结构是出于性能的考虑,因为BitSet提供and和or这种操作,需要对两个BitSet中的所有bit位做and或者or,实现的时候需要遍历所有的数组元素。使用long能够使得循环的次数降到最低,所以Java选择使用long数组作为BitSet的内部存储结构。
从数据在栈上的存储来说,使用long和byte基本是没有什么差别的,除了编译器强制地址对齐的时候,使用byte最多会浪费7个字节(强制按照8的倍数做地址对其),另外从内存读数组元素的时候,也是没有什么区别的,因为汇编指令有对不同长度数据的mov指令。所以说,JDK选择使用long数组作为BitSet的内部存储结构的根本原因就是在and和or的时候减少循环次数,提高性能。
例如我们进行BitSet中的and, or,xor操作时,要对整个bitset中的bit都进行操作,需要依次读出bitset中所有的word,如果是long数组存储,我们可以每次读入64个bit,而int数组存储时,只能每次读入32个bit。另外我们在查找bitset中下一个置为1的bit时,word首先会和0进行比较,如果word的值为0,则表示该word中没有为1的bit,可以忽略这个word,如果是long数组存储,可以一次跳过64个bit,如果是int数组存储时,一次只能跳过32个bit。