原理
众所周知,Java的BitSet使用一个Long(一共64位)的数组中的每一位(bit)是否为1
来表示当前Index的数存在不。但是BitSet又是如何实现的呢?其实只需要理解其中的两个方法:
- set
- get
就能够理解BitSet的实现原理是什么了。
set
先看源代码:
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex);
words[wordIndex] |= (1L << bitIndex); // Restores invariants
checkInvariants();
}
抛过判断小于0, 从第 4行开始读取
wordIndex
/**
* Given a bit index, return word index containing it.
*/
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
private final static int ADDRESS_BITS_PER_WORD = 6;
这里ADDRESS_BITS_PER_WORD
的值是6,那么最先想到的问题就是:为什么是6呢?而不是其他值呢?
答案其实很简单,还记得在最开始提到的:BitSet里使用一个Long数组里的每一位来存放当前Index是否有数存在。
因为在Java里Long类型是64位,所以一个Long可以存储64个数,而要计算给定的参数bitIndex
应该放在数组(在BitSet里存在word
的实例变量里)的哪个long里,只需要计算:bitIndex / 64
即可,这里正是使用>>
来代替除法(因为位运算要比除法效率高)。而64正好是2的6次幂,所以ADDRESS_BITS_PER_WORD
的值是6。
通过wordIndex
函数就能计算出参数bitIndex
应该存放在words
数组里的哪一个long里。
expandTo
private void expandTo(int wordIndex) {
int wordsRequired = wordIndex+1;
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired);
wordsInUse = wordsRequired;
}
}
从上面已经知道在BitSet里是通过一个Long数组(words
)来存放数据的,这里的expandTo
方法就是用来判断words
数组的长度是否大于当前所计算出来的wordIndex
(简单的说,就是能不能存的下),如果超过当前words
数组的长度(记录在实例变量wordsInUse
里),也即是存不下,则新加一个long数到words
里(ensureCapacity(wordsRequired)所实现的。)。
Restores invariants
words[wordIndex] |= (1L << bitIndex); // Restores invariants
这一行代码可以说是BitSet的精髓了,先不说什么意思,我们先看看下面代码的输出:
System.out.println(Integer.toBinaryString(1<<0));
System.out.println(Integer.toBinaryString(1<<1));
System.out.println(Integer.toBinaryString(1<<2));
System.out.println(Integer.toBinaryString(1<<3));
System.out.println(Integer.toBinaryString(1<<4));
System.out.println(Integer.toBinaryString(1<<5));
System.out.println(Integer.toBinaryString(1<<6));
System.out.println(Integer.toBinaryString(1<<7));
结果为:
1
10
100
1000
10000
100000
1000000
10000000
从而发现,上面所有的输出力,1
所在的位置,正好是第1,2,3,4,5,6,7,8(Java数组的Index从0开始)位。而BitSet正是通过这种方式,将所给的bitIndex
所对应的位设置成1,表示这个数已经存在了。这也解释了(1L << bitIndex)
的意思(注意:因为BitSet是使用的Long,所以要使用1L来进行位移)。
搞懂了(1L << bitIndex)
,剩下的就是用|
来将当前算出来的和以前的值进行合并了words[wordIndex] |= (1L << bitIndex);
。
剩下的checkInvariants
就没什么好解释的了。
get
搞懂了set
方法,那么get
方法也就好懂了,整体意思就是算出来所给定的bitIndex
所对应的位数是否为1
即可。先看看代码:
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
checkInvariants();
int wordIndex = wordIndex(bitIndex);
return (wordIndex < wordsInUse)
&& ((words[wordIndex] & (1L << bitIndex)) != 0);
}
计算wordIndex
在上面set方法里已经说明了,就不再细述。这个方法里,最重要的就只有:words[wordIndex] & (1L << bitIndex)
。这里(1L << bitIndex)
也已经做过说明,就是算出一个数,只有bitIndex
位上为1,其他都为0,然后再和words[wordIndex]
做&
计算,如果words[wordIndex]
数的bitIndex
位是0
,则结果就是0
,以此来判断参数bitIndex
存在。
贴上API
Modifier and Type | Method and Description |
|
执行此参数位置位的此目标位设置的逻辑 AND 。 |
|
清除所有的位,这 |
|
返回此 |
|
将此BitSet中的所有位设置为 |
|
将索引指定的位设置为 |
|
将指定的 |
|
克隆这个 |
|
将此对象与指定对象进行比较。 |
|
将指定索引处的位设置为其当前值的补码。 |
|
将指定的每一位 |
|
返回具有指定索引的位的值。 |
|
返回一个新 |
|
返回此位集的哈希码值。 |
|
如果指定,则返回true |
|
如果此 |
|
返回这个 |
|
返回在指定的起始索引上或之后设置为 |
|
返回在指定的起始索引上或之后发生的第一个位的索引设置为 |
|
使用位设置参数执行该位的逻辑 或 。 |
|
返回被设置为最接近的位的索引 |
|
返回被设置为最接近的位的索引 |
|
将指定索引处的位设置为 |
|
将指定索引处的位设置为指定值。 |
|
将指定的 |
|
将指定的 |
|
返回此 |
IntStream |
返回此 |
|
返回一个包含该位集中所有位的新字节数组。 |
|
返回一个包含该位集合中所有位的新长数组。 |
String |
返回此位集的字符串表示形式。 |
|
返回包含给定字节数组中所有位的新位集合。 |
|
返回一个新的位集,其中包含给定字节缓冲区中位置和极限之间的所有位。 |
|
返回包含给定长数组中所有位的新位集。 |
|
返回包含给定长缓冲区中其位置和极限之间的所有位的新位集合。 |
|
使用位设置参数执行该位的逻辑 异或 。 |