JAVA类库分析之BitSet

JAVA类库分析之BitSet

1.BitSet概述

      BitSet实现了一种比特位的向量,能够自动增长,用途很广泛。如在bloom filter中会用到BitSet来标识某一位是否置位等。初始情况下所有位都为false。主要的变量如下表中所示,下面分析的时候会详细介绍这些变量的用处。首先可以注意到用来存储位向量的数组wordslong类型,也就是说每一个值可以保存64位信息,所以ADDRESS_BITS_PER_WORD=6。变量wordsInUse用来表示当前有多少个words在用,初始为0,它的值在每次扩容words数组后更新,第一次调用set(int index)方法时一定会更新wordsInUse变量,因为初始值0会小于需要的words数目。这里的word都是long类型,即64位的。此外,变量sizeInSticky表示用户是否指定了words的数目。

private finalstatic int ADDRESS_BITS_PER_WORD = 6;

  privatefinal staticint BITS_PER_WORD = 1 <<ADDRESS_BITS_PER_WORD;

 privatefinal staticint BIT_INDEX_MASK =BITS_PER_WORD - 1;  

/**

     * The internal field corresponding to the serialField "bits".

     */

    privatelong[]words;

 

    /**

     * The number of words in the logical size of this BitSet.

     */

    privatetransient intwordsInUse = 0;

 

    /**

     * Whether the size of "words" is user-specified. If so, we assume the user

     * knows what he's doing and try harder to preserve it.

     */

    privatetransient booleansizeIsSticky =false;

 

2.方法分析

BitSet()

public BitSet() {

       initWords(BITS_PER_WORD); //BITS_PER_WORD=1<<6=64

       sizeIsSticky =false;

}

 

public BitSet(int nbits) {

       //nbits can't be negative; size 0 is OK

       if (nbits < 0)

           thrownew NegativeArraySizeException("nbits < 0: " + nbits);

 

       initWords(nbits);

       sizeIsSticky =true;

}

privatevoidinitWords(int nbits) {

       words =newlong[wordIndex(nbits - 1) + 1];

}

/*判断指定比特位在数组中的索引,

*如第62位属于words[0],而第64位属于words[1](62>>6=0, 64>>6=1)

*/

privatestaticintwordIndex(int bitIndex) {

// ADDRESS_BITS_PER_WORD=6

       return bitIndex >>ADDRESS_BITS_PER_WORD;

}

       由构造方法代码可知,当我们不指定比特数目时,则默认会用64来初始化BitSet,即words数组大小为1,并设置sizeIsStickyfalse。如果指定了比特数目,则用指定的数目来创建words数组,并设置sizeInStickytrue

 

set(int index):设置指定的第index位为true(其实就是置为1)。

publicvoid set(int bitIndex) {

        //判断bitIndex不能小于0

       if (bitIndex < 0)

           thrownew IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

        //计算这一位在words数组中的位置

       int wordIndex =wordIndex(bitIndex);

        //判断是否需要扩展words数组大小

       expandTo(wordIndex);

         

       words[wordIndex] |= (1L << bitIndex);// Restores invariants

       checkInvariants();//检查一些条件是否满足。

}

privatevoid expandTo(int wordIndex) {

       int wordsRequired = wordIndex + 1;

       if (wordsInUse < wordsRequired) {

            //如果当前使用的words数目小于需要的words数目,则扩展words数组至需要的大小,并设置wordsInUse值为当前需要的words数目。

           ensureCapacity(wordsRequired);

           wordsInUse = wordsRequired;

       }

}

privatevoid 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;

       }

}

        首先求出需要设置的位在words数组中的位置,然后判断是否需要增加words数组大小。

        如果需要增大words数组,则调用ensureCapacity方法实现,该方法设置新数组大小为words数组原来大小的2倍和wordsRequired的较大值,创建新数组,并将原来words数组的元素全部拷贝到新创建数组中,更新words为新数组的引用。同时会设置sizeIsSticky = false

        否则,直接将index位置位,即通过这一行代码实现置位:words[wordIndex] |= (1L << bitIndex);其中wordIndexindexwords数组中的位置,后面通过按位或操作对bitIndex位置位。需要注意的是java中的移位操作会模除位数,也就是说,long类型的移位会模除64。例如对long类型的值左移65位,实际是左移了65%64=1位。

       这里可以通过一个例子来加深印象,比如如下代码:

BitSet bs = new BitSet(); //1

bs.set(12); //2

bs.set(63); //3

bs.set(64); //4

       第一行创建一个BitSet对象,此时调用无参数的构造函数,初始化比特位为64,也就是说words数组初始大小为1。接着调用set(12)设置第12位为1,在执行set方法的过程中,会调用expandTo()方法来判断是否需要扩容,最终确定是否扩容是由ensureCapacity方法决定的。由于第2行代码是第一次调用set方法,此时wordsInUse=0 < wordsRequired=1,所以会设置wordsInUse=1,需要注意的是此时并没有扩容。第3行代码置位第63位,不需要扩容。第4行代码置位第64位,此时wordsRequired=2 > wordsInUse=1,所以会扩容,同时设置wordsInUse=2,新的words数组大小为2

 

get(int index):index位被置位,则返回true,否则返回false

publicboolean get(int bitIndex) {

       if (bitIndex < 0)

           thrownew IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

       checkInvariants();

 

       int wordIndex =wordIndex(bitIndex);

       return (wordIndex <wordsInUse)

              && ((words[wordIndex] & (1L << bitIndex)) != 0);

}

      该方法首先得到获取位在words数组中的索引,然后返回相应值的对应的位的是否置位情况,主要通过words[wordIndex] & (1L << bitIndex)来实现。这个方法需要注意的一点就是,如果我们给的参数在words数组中的索引wordIndex大于或等于wordsInUse,则会直接返回false

考虑下面的代码,BitSet默认大小为64位,words数组大小为1,此时如果调用bs.get(126),则会直接返回false。因为126对应的wordIndex=1,而此时wordsInUse=0wordIndex>wordsInUse,所以get方法的return语句的后半部分都不会执行了。

BitSet bs = new BitSet();

System.out.println(bs.get(126));

 

 

Clear(int index):清除index位的置位标志。

publicvoid clear(int bitIndex) {

       if (bitIndex < 0)

           thrownew IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

 

       int wordIndex =wordIndex(bitIndex);

       if (wordIndex >=wordsInUse)

           return;

 

       words[wordIndex] &= ~(1L << bitIndex);

 

       recalculateWordsInUse();

       checkInvariants();

}

 

/**

     * Set the field wordsInUse with the logical size in words of the bit set.

     * WARNING:This method assumes that the number of words actually in use is

     * less than or equal to the current value of wordsInUse!

     */

privatevoid recalculateWordsInUse() {

       // Traverse thebitset 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

}

       该方法为set方法的逆过程,即先找到index位在words数组中的位置wordIndex,然后判断如果wordsIndex>=wordsInUse,则什么也不做返回;否则,将words[wordIndex]对应的bitIndex位清0,然后调用方法recalculateWordsInUse()重新计算正在使用的words大小,该方法会重新设置wordsInUse的值,这只是一个逻辑上的值,实际words数组大小并没有变。例如下面的代码:

BitSet bs = new BitSet();

bs.set(12);

bs.clear(12);

System.out.println(bs.size()); //result: 64

//public int size() {words.length * BITS_PER_WORD}

       该例子中,默认BitSet64位,先调用set(12)将第12位置位,然后调用clear(12)清除第12位,这时候recalculateWordsInUse()方法会设置wordsInUse值为0。但是,此时调用bs.size()还是会返回64,因为wordsInUse只是逻辑上的值,表示当前用到的words数目,而实际上我们words数组大小还是为1,所以size()方法返回64

 

其他方法

    /**

     * Sets all of the bits in this BitSet to<code>false</code>.

     *

     * @since 1.4

     */

publicvoidclear() {

       while (wordsInUse > 0)

           words[--wordsInUse] = 0;

}

/*这个方法将[fromIndex, toIndex)范围内的位都清零。注意包含开头不包含结尾。这里的位操作要特别注意。*/

publicvoidclear(int fromIndex,int toIndex) {

       checkRange(fromIndex, toIndex);

 

       if (fromIndex == toIndex)

           return;

 

       int startWordIndex =wordIndex(fromIndex);

       if (startWordIndex >=wordsInUse)

           return;

 

       int endWordIndex =wordIndex(toIndex - 1);

       if (endWordIndex >=wordsInUse) {

           toIndex = length();

           endWordIndex = wordsInUse - 1;

       }

 

       long firstWordMask =WORD_MASK << fromIndex;

       long lastWordMask =WORD_MASK >>> -toIndex; //注意这里是算术移位

       if (startWordIndex == endWordIndex) {

           // Case 1: One word

           words[startWordIndex] &= ~(firstWordMask & lastWordMask);

       } else {

           // Case 2: Multiple words

           // Handle first word

           words[startWordIndex] &= ~firstWordMask;

 

           // Handle intermediate words, if any

           for (int i = startWordIndex + 1; i < endWordIndex; i++)

              words[i] = 0;

 

           // Handle last word

           words[endWordIndex] &= ~lastWordMask;

       }

 

       recalculateWordsInUse();

       checkInvariants();

    }

 

 

3.总结

        整体来说,BitSet实现机制并不复杂,常用的方法也就几个,所以就不赘述其他的方法了,分析的匆忙,若有错误的地方,还请各位大虾指正。

 

你可能感兴趣的:(java,user,filter,less,存储,扩展)