一. 位运算
在学习 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); // 1long 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); // 64long 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 = 0bs.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^6return 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 = 1000000words[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;
}