BitSet使用:
问题:对40亿个数据进行排序,数据类型为 int,无相同数据。
思考:关于40亿个数据的排序,首先想如何存储呢?一个int 4个字节,也就是160亿个字节,也就是大概有16GB的数据,现在所有的计算机估计没有这么大的内存吧,所以我们就可以文件归并排序,也可以分段读入数据在进行Qsort,但是都需要不停地读入文件,可以想象不停地读取文件硬件操作会有多么浪费时间。
我们这样都是用4个字节来存储了一个数据,在计算机里都是用二进制进行表示:
例如 5 :0000 0000 0000 0000 0000 0000 0000 0101
现在引入BitSet,所谓BitSet就是用一个bit来表示一个数据。平时32位存储一个数据,我们可以换一种想法,用一个字节32位来存储0-31这32个数据,例如我们对2,1,5,12这四个数据进行由小到大的排序,首先把32位初始化为0,我们可以把这4个数据存储为0000 0000 0000 0000 0001 0000 0010 0110
我们就把32位中的分别把 2 1 5 12位置为1,然后从第0位开始遍历,看相应位是否为1,为1就进行输出,就完成了数据从小到大的排序。
再返回原题应用BitSet就可以把16GB的存储空间缩小为16GB/32 = 512M,就可以大大减少读取文件的工作。直接读一次文件存入内存,然后遍历输出就完成了排序。
优点:既大量节省了空间,又把时间复杂度降低到O(n)。
不足:如果数据过于稀疏就会有大量无用遍历,浪费时间。
源码
首先看构造方法:
/**
* Creates a new bit set. All bits are initially {@code false}.
*/
public BitSet() {
initWords(BITS_PER_WORD);
sizeIsSticky = false;
}
这里面用到了BITS_PER_WORD和initWords方法,声明如下:
private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private void initWords(int nbits) {
words = new long[wordIndex(nbits-1) + 1];
}
wordIndex方法:
/**
* Given a bit index, return word index containing it.
*/
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
返回的是bitIndex右移6位的值。可以理解为除64的结果。
可以看到initWords方法创建了一个long数组,数组的大小为wordIndex(nbits-1) + 1,为什么是这个呢?
我们假设几个nbits的值:
nbits == 3 那么wordIndex返回0,数组大小为1
nbits == 64 wordIndex返回0,数组大小为1
nbits == 65 wordIndex返回1,数组大小为2
看到这里大家应该明白了,事实上BitSet使用long数组来存储每一位的boolean值的。这里long数组为words,而wordIndex则返回给定bitIndex下,在words中的位置。
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();
}
其中expandTo()方法保证不会出现long数组越界,当wordIndex的值大于数组的长度时,则会自动扩大数组的大小。expandTo方法及相关方法(checkInvariants)在文章最后给出。
get方法:
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大于实际长度时,会直接返回FALSE,否则会根据对应位的0 、1返回。
/**
* Ensures that the BitSet can hold enough words.
* @param wordsRequired the minimum acceptable number of words.
*/
private void ensureCapacity(int wordsRequired) {
if (words.length < wordsRequired) {
// Allocate larger of doubled size or required size
int request = Math.max(2 * words.length, wordsRequired);
words = Arrays.copyOf(words, request);
sizeIsSticky = false;
}
}
/**
* Ensures that the BitSet can accommodate a given wordIndex,
* temporarily violating the invariants. The caller must
* restore the invariants before returning to the user,
* possibly using recalculateWordsInUse().
* @param wordIndex the index to be accommodated.
*/
private void expandTo(int wordIndex) {
int wordsRequired = wordIndex+1;
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired);
wordsInUse = wordsRequired;
}
}
private void checkInvariants() {
assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
assert(wordsInUse >= 0 && wordsInUse <= words.length);
assert(wordsInUse == words.length || words[wordsInUse] == 0);
}
例子代码如下:
import java.util.BitSet;
public class TestBitSet {
public static void main(String[] args) {
int[] data={2,42,5,6,6,18,33,15,25,31,28,37};
//默认64,超过集合容量会扩大,从而影响效率,所以初始化时最好按照数组最大值进行初始化
BitSet bs=new BitSet(64);
System.out.println("bs.size()--"+bs.size());
//默认BitSet是false
for(int i=0;iset(data[i], true) ;
}
for(int i=0;iif(bs.get(i)){
System.out.println(i);
}
}
}
}