一. 位运算
在学习 BitSet 之前,我们先看一下位运算;
下述我们拿 long 来接收位运算的返回值,我们知道 long 为 64 位,8 个字节;
// 1. 右移一位,表示 num / 2;右移 6 位,表示 num / 2^6
// 10000 2^4
// 1000000 2^6
// 111111 2^6 - 1
long a = 64 >> 6;
System.out.println(a); // 1
long b = 63 >> 6;
System.out.println(b); // 0
// 2. 左移一位,表示 num * 2;左移 6 位,表示 num * 2^6
// 如果左移 70 位,超过 64 位会溢出,本质还是左移 6 位,也就是 num * 2^6
long c = 1 << 6;
System.out.println(c); // 64
long d = 1 << 70;
System.out.println(d); // 64
二. BitSet
1. 创建和初始化
可以通过无参构造器或指定初始容量的构造器创建 BitSet;
BitSet bs = new BitSet(); // 默认初始容量为 64 位
BitSet bs = new BitSet(10); // 指定初始容量为 10 位
BitSet 中是以 long[] 来存储位数的,所以 BitSet 的实际存储容量是以 64 的倍数增长的,因此,即使我们指定了 10 位的初始容量,BitSet 的实际容量也是 64 位;
2. 读取容量和长度
BitSet 提供了 size() 和 length() 来获取其实际存储容量和逻辑长度;
- size():返回 BitSet 实际存储的位数,该值是 64 位的倍数;
- length():返回 BitSet 中最高设置位的索引加 1,这表示逻辑上的长度;
BitSet bs = new BitSet(10);
int size = bs.size();
System.out.println("size = " + size); // size = 64
int length = bs.length();
System.out.println("length = " + length); // length = 0
bs.set(1);
bs.set(3);
bs.set(5);
bs.set(7, false);
length = bs.length();
System.out.println("length = " + length); // length = 6
3. 设置和清除位
BitSet 中提供了很多方法来设置和清除位,包括 set() 和 clear();
BitSet bs = new BitSet();
bs.set(2); // 将第 2 位设为 true
bs.clear(2); // 将第 2 位设为 false
bs.set(3, false); // 将第 3 位设为 false
// 可以通过传递范围参数来批量设置或清除位,左闭右开
bs.set(0, 5); // 将第 0 到第 4 位设为 true
bs.clear(0, 5); // 将第 0 到第 4 位设为 false
4. 检查位状态
可以通过 get(int index) 来检查指定位的状态;
BitSet bitSet = new BitSet();
bitSet.set(0, true);
bitSet.set(1);
bitSet.set(2, false);
System.out.println(bitSet.get(1)); // true
System.out.println(bitSet.get(2)); // false
5. 其他常用方法
- isEmpty():判断 BitSet 是否为空;
- cardinality():获取 BitSet 中设置为 true 的位的数量;
- toString():将 BitSet 转换为字符串输出,只输出 true 的位;
BitSet bitSet = new BitSet();
bitSet.set(0, true);
bitSet.set(1);
bitSet.set(2, false);
System.out.println(bitSet.isEmpty()); // false
System.out.println(bitSet.cardinality()); // 2
System.out.println(bitSet.toString()); // {0, 1}
6. 遍历
6.1 遍历1位
BitSet 的 stream() 会返回一个 stream 流,其中包含了所有被设置为 1 的位的索引;
BitSet bitSet = new BitSet();
bitSet.set(1);
bitSet.set(3);
bitSet.set(7);
bitSet.stream().boxed().forEach(item -> System.out.println(item)); // 1 3 7
6.2 遍历0位
BitSet 的 nextClearBit(int index) 会返回大于或等于给定索引的第一个未设置位的位置;
BitSet 中没有现有的 api 轮询设置位为 0 的位的索引,需要结合 nextClearBit();
BitSet bitSet = new BitSet();
bitSet.set(1);
bitSet.set(3);
bitSet.set(7);
for (int i = bitSet.nextClearBit(0);
i < bitSet.size();
i = bitSet.nextClearBit(i+1)) {
System.out.println(i);
}
三. 源码分析
我们对几个比较常用的方法进行源码分析;
1. 构造方法
举个例子,如果 nbits 为 64,wordIndex(64-1) + 1 = 1;
如果 nbits 为 65,wordIndex(65-1) + 1 = 2;
// -------------------------------- BitSet ---------------------------------
public BitSet(int nbits) {
if (nbits < 0)
throw new NegativeArraySizeException("nbits < 0: " + nbits);
// 初始化 long words[] 数组
initWords(nbits);
sizeIsSticky = true;
}
// -------------------------------- BitSet ---------------------------------
private void initWords(int nbits) {
// wordIndex(nbits-1) 计算 long words[] 数组的初始化大小
words = new long[wordIndex(nbits-1) + 1];
}
// -------------------------------- BitSet ---------------------------------
private static int wordIndex(int bitIndex) {
// 计算 bitIndex 需要的数组大小
// 其实就是 bitIndex 右移 6 位,也就是 bitIndex / 2^6
return bitIndex >> 6;
}
2. set()
// -------------------------------- BitSet ---------------------------------
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
// 1. 计算在 words[] 中的下标
int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex);
// 2. 作或运算
// 如果 bitIndex = 70,wordIndex = 1,1 << 70 = 1000000
words[wordIndex] |= (1L << bitIndex);
checkInvariants();
}
3. get()
// -------------------------------- BitSet ---------------------------------
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
checkInvariants();
// 1. 计算在 words[] 中的下标
int wordIndex = wordIndex(bitIndex);
// 2. 不能超出 wordsInUse,作与运算
return (wordIndex < wordsInUse)
&& ((words[wordIndex] & (1L << bitIndex)) != 0);
}
4. size()
// -------------------------------- BitSet ---------------------------------
public int size() {
// long words[] 数组的长度 * 64,其实就是得到位的长度
return words.length * 64;
}