java.util.BitSet使用与分析

Posted lfdanding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java.util.BitSet使用与分析相关的知识,希望对你有一定的参考价值。

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;i<data.length;i++)
                bs.set(data[i], true) ;
            
           for(int i=0;i<bs.size();i++)
               if(bs.get(i))
                   System.out.println(i);
               
           
          

以上是关于java.util.BitSet使用与分析的主要内容,如果未能解决你的问题,请参考以下文章

Java中BitSet使用(转)

Java:计算 java.util.BitSet 中设置的位数

BitSet 与整数/长整数

位集合类BitSet

java.util有那些工具类

BitSet 的 size() 方法的原因是啥?