- 推荐阅读:Java1.8-BitSet源码分析
1、继承体系及属性
public class BitSet implements Cloneable, java.io.Serializable {
// 保存bit的数组
private long[] words;
// 表示在数组words中已经使用的数的个数
private transient int wordsInUse = 0; //用于判断检查,扩容等相关的参数
// 保存bit的words数组的位数是否由用户指定
private transient boolean sizeIsSticky = false;
//long类型的地址位数,即进行操作时位移的个数
private final static int ADDRESS_BITS_PER_WORD = 6; //1 << 6 = 64
//表示位移的数字大小
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; //64
private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1; //63
//称为掩码,用于位移时进行运算
private static final long WORD_MASK = 0xffffffffffffffffL; //-1L
}
2、构造方法及关联的方法
//默认构造方法,默认初始化数组容量为 1
public BitSet() {
initWords(BITS_PER_WORD);
sizeIsSticky = false;
}
//根据传入的参数初始化数组容量
public BitSet(int nbits) {
// 位数不能是负数,但可以是0
if (nbits < 0)
throw new NegativeArraySizeException("nbits < 0: " + nbits);
initWords(nbits);
sizeIsSticky = true;
}
//long 数组的大小为:nbits / 64 + nbits % 64
private void initWords(int nbits) {
words = new long[wordIndex(nbits-1) + 1];
}
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
initWords
与wordIndex
可作为数组切分的标准方法参考
3、set 方法
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
// 根据传入的位索引定位到long数组中哪一个元素
int wordIndex = wordIndex(bitIndex);
// 判断是否需要扩容
expandTo(wordIndex);
//进行位与运算,将定位到的 long 数组中的元素对应位置上的位设置为 1
words[wordIndex] |= (1L << bitIndex);
// 使用断言校验wordsInUse变量
checkInvariants();
}
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
//wordIndex 为 long[] 的 index
private void expandTo(int wordIndex) {
int wordsRequired = wordIndex + 1;
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired);
wordsInUse = wordsRequired;
}
}
private void ensureCapacity(int wordsRequired) {
// 如果原数组长度小于实际数组所需容量,扩容
if (words.length < wordsRequired) {
// 扩容2倍或实际所需容量,取两者之间的最大值
int request = Math.max(2 * words.length, wordsRequired);
words = Arrays.copyOf(words, request);
sizeIsSticky = false;
}
}
public static long[] copyOf(long[] original, int newLength) {
long[] copy = new long[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
private void checkInvariants() {
assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
assert(wordsInUse >= 0 && wordsInUse <= words.length);
assert(wordsInUse == words.length || words[wordsInUse] == 0);
}
大致步骤如下:
- 判断传入的参数位索引是否小于 0
- 根据传入的位索引定位到 long 数组中哪一个元素
- 根据 wordsInUse 变量判断 long 数组中实际所使用的元素个数是否小于实际所需要的个数
- 若是,再判断数组的长度是否小于实际所需要的数组容量
- 若小于,比较扩容两倍后的容量与实际所需要的容量,取两者之间的较大者进行扩容
- 进行位与运算,将定位到的 long 数组中的元素对应位置上的位设置为 1
- 断言校验 wordsInUse 变量,所有 public 方法都会调用
/**
* 将指定位索引处的值设置为指定的值
* 1. 若 value 是 true,设为 1
* 2. 若为 false,设为 0
*/
public void set(int bitIndex, boolean value) {
if (value)
set(bitIndex);
else
// clear方法来清除对应索引处的值,也就是设置为0
clear(bitIndex);
}
public void clear(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);
if (wordIndex >= wordsInUse)
return;
words[wordIndex] &= ~(1L << bitIndex); //反、与运算,将指定位置设为 0
recalculateWordsInUse();
checkInvariants();
}
//校验 long[] 是否保存的值均有效(非 0)并更新 wordsInUse 的有效值
private void recalculateWordsInUse() {
// Traverse the bitset until a used word is found
int i;
for (i = wordsInUse - 1; i >= 0; i--)
if (words[i] != 0)
break;
wordsInUse = i + 1; // The new logical size
}
- 推荐阅读:Java 位移运算
//设置指定范围内的位索引(不包含fromIndex,包含 toIndex)
public void set(int fromIndex, int toIndex) {
// 1. 校验开始索引和结束索引是否合法,不合法抛异常
checkRange(fromIndex, toIndex);
// 2. 若两个索引相同,直接结束程序流程,不进行任何操作
if (fromIndex == toIndex)
return;
// 3. 定位索引所在数组中的位置
int startWordIndex = wordIndex(fromIndex);
int endWordIndex = wordIndex(toIndex - 1);
// 确认是否扩容
expandTo(endWordIndex);
// 计算掩码
long firstWordMask = WORD_MASK << fromIndex;
long lastWordMask = WORD_MASK >>> -toIndex;
// 如果定位的索引在数组的同一个元素中
if (startWordIndex == endWordIndex) {
// 先对firstWordMask和lastWordMask进行与运算,再进行位或运算(补码运算)
words[startWordIndex] |= (firstWordMask & lastWordMask);
} else {
// 如果不在数组的同一个元素中
// 对第一个元素进行位于操作,将对应索引的位设置为1
words[startWordIndex] |= firstWordMask;
// 如果元素有多个,全部置为1
for (int i = startWordIndex+1; i < endWordIndex; i++)
words[i] = WORD_MASK;
// 对最后一个元素也进行位于操作,将对应索引的位设置为1
words[endWordIndex] |= lastWordMask;
}
checkInvariants();
}
private static void checkRange(int fromIndex, int toIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
if (toIndex < 0)
throw new IndexOutOfBoundsException("toIndex < 0: " + toIndex);
if (fromIndex > toIndex)
throw new IndexOutOfBoundsException("fromIndex: " + fromIndex + " > toIndex: " + toIndex);
}
4、get 方法
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
checkInvariants();
// 定位到具体的long类型
int wordIndex = wordIndex(bitIndex);
// 对应索引位上进行位与操作,判断是否等于0来返回对应true和false
return (wordIndex < wordsInUse) && ((words[wordIndex] & (1L << bitIndex)) != 0);
}
5、flip 方法
//反转某一个索引位,即将 1 置为 0,将 0 置为 1
public void flip(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex);
// 通过位异或操作进行
words[wordIndex] ^= (1L << bitIndex);
recalculateWordsInUse();
checkInvariants();
}
6、nextSetBit 方法
//获取指定索引处及之后第一个 1 的位置
public int nextSetBit(int fromIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
checkInvariants();
int u = wordIndex(fromIndex);
if (u >= wordsInUse)
return -1;
// 获取word的实际值
long word = words[u] & (WORD_MASK << fromIndex);
while (true) {
if (word != 0)
return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word);
if (++u == wordsInUse)
return -1;
word = words[u];
}
}
该方法用于遍历 BitSet:
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
if (i == Integer.MAX_VALUE) {
break;
}
}
7、size,length,cardinality 方法
//返回数组大小与 64 的乘积
public int size() {
return words.length * BITS_PER_WORD;
}
//返回 BitSet 的逻辑大小
public int length() {
if (wordsInUse == 0)
return 0;
return BITS_PER_WORD * (wordsInUse - 1)
+ (BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1]));
}
//返回 BitSet 中实际元素的个数
public int cardinality() {
int sum = 0;
for (int i = 0; i < wordsInUse; i++)
sum += Long.bitCount(words[i]);
return sum;
}
public static int numberOfLeadingZeros(long i) {
if (i == 0)
return 64;
int n = 1;
int x = (int)(i >>> 32);
if (x == 0) { n += 32; x = (int)i; }
if (x >>> 16 == 0) { n += 16; x <<= 16; }
if (x >>> 24 == 0) { n += 8; x <<= 8; }
if (x >>> 28 == 0) { n += 4; x <<= 4; }
if (x >>> 30 == 0) { n += 2; x <<= 2; }
n -= x >>> 31;
return n;
}
public static int bitCount(long i) {
i = i - ((i >>> 1) & 0x5555555555555555L);
i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);
i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
i = i + (i >>> 8);
i = i + (i >>> 16);
i = i + (i >>> 32);
return (int)i & 0x7f;
}
注意:BitSet 可以动态扩容,但不可以动态减少,所以可能会出现 OutOfMemory
8、案例
- 推荐阅读:BitSet的使用场景及简单示例
//进行数字排序
public static void sortArray() {
int[] array = new int[] { 423, 700, 9999, 2323, 356, 6400, 1,2,3,2,2,2,2 };
BitSet bitSet = new BitSet(2 << 13);
// 虽然可以自动扩容,但尽量在构造时指定估算大小,
// 我们要看一下数组中的最大值,然后指定大致的容量,默认为64
System.out.println("BitSet size: " + bitSet.size());
for (int i = 0; i < array.length; i++) {
bitSet.set(array[i]);
}
//剔除重复数字后的元素个数
int bitLen=bitSet.cardinality();
System.out.println("要排序的数组容量:" + array.length);
System.out.println("剔除重复数字后的元素个数:" + bitLen);
//进行排序,即把bit为true的元素复制到另一个数组
int[] orderedArray = new int[bitLen];
int k = 0;
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
orderedArray[k++] = i;
}
System.out.println("After ordering: ");
for (int i = 0; i < bitLen; i++) {
System.out.print(orderedArray[i] + "\t");
}
System.out.println("\n通过迭代器获取排序后的元素:");
//或直接迭代BitSet中bit为true的元素
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
System.out.print(i+"\t");
}
System.out.println("\n---------------------------");
}
//结果:
BitSet size: 16384
要排序的数组容量:13
剔除重复数字后的元素个数:9
After ordering:
1 2 3 356 423 700 2323 6400 9999
通过迭代器获取排序后的元素:
1 2 3 356 423 700 2323 6400 9999
---------------------------