Java BitSet 源碼解析(1)

參考:

java.util.BitSet

Java BitSet類


查看類 ArrayListremoveIf 方法源碼時,發現其使用 BitSet 類來存儲待刪除的元素下標

之前沒有接觸過這個類,瞭解之後發現其在數據查詢和存儲方面有很大用處


主要內容:

  1. BitSet 淺析
  2. 類變量和常量
  3. 構造器
  4. set
  5. clear -(2)
  6. get
  7. flip - (3)
  8. valueOf
  9. 位運算(and, andNot, or, xor
  10. next
  11. previous - (4)
  12. 判空 / 判斷交集
  13. 大小(length, size, cardinality
  14. toByteArraytoLongArray
  15. BitSet 分析
  16. Java 素數查找
  17. 小結

BitSet 淺析

BitSet,顧名思義,它表示一個位集,在每一位上僅有兩個選項 - true 或者 false

在實際操作中,如果需要對一堆數據進行某個條件的判斷,那麼這一類問題都可以使用類 BitSet 來解決


類變量和常量

BitSet 包含的變量和常量如下所示

private long[] words;

在類 BitSet 內部,使用長整型(long)數組 words 來保存位集

/*
 * BitSets are packed into arrays of "words."  Currently a word is
 * a long, which consists of 64 bits, requiring 6 address bits.
 * The choice of word size is determined purely by performance concerns.
 */
private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;

/* Used to shift left or right for a partial word mask */
private static final long WORD_MASK = 0xffffffffffffffffL;

長整型整數佔 64 位,進行左右移動時,使用 6 位二進制即可

private transient int wordsInUse = 0;

變量 wordsInUse 表示在數組 words 中已使用的整數個數

private transient boolean sizeIsSticky = false;

變量 sizeIsSticky 表示是否用戶指定了數組 words 的大小(該變量在方法 clone() 和序列化方法中有用到

private static final long serialVersionUID = 7997698588986878753L;

暫時還不清楚該常量的用處


構造器

BitSet 提供了 2 個公有構造器和 1 個私有構造器:

public BitSet()
public BitSet(int nbits)
private BitSet(long[] words)

下面介紹兩個公有構造器的使用

  • BitSet()

源碼如下:

public BitSet() {
    initWords(BITS_PER_WORD);
    sizeIsSticky = false;
}

首先調用函數 initWords,輸入參數爲常量 BITS_PER_WORD(大小爲 64

然後設置變量 sizeIsStickyfalse,即用戶沒有設定數組 words 大小

  • BitSet(int nbits)

源碼如下:

public BitSet(int nbits) {
    // nbits can't be negative; size 0 is OK
    if (nbits < 0)
        throw new NegativeArraySizeException("nbits < 0: " + nbits);

    initWords(nbits);
    sizeIsSticky = true;
}

輸入參數 nbits 表示初始化位集的大小

首先判斷參數 nbits 是否符合條件,如果不符合,拋出 NegativeArraySizeException 異常

接下來調用函數 initWords,輸入參數爲 nbits

最後,設置 sizeIsStickytrue,表示用戶指定了數組 words 大小

initWords

源碼如下:

private void initWords(int nbits) {
    words = new long[wordIndex(nbits-1) + 1];
}

該方法中,爲 words 創建長度爲 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;
}

方法 wordIndex 用於計算位集中下標爲 bitIndex 的位在數組 words 中的下標


set

BitSet 提供了 4set 函數:

public void set(int bitIndex)
public void set(int bitIndex, boolean value)
public void set(int fromIndex, int toIndex)
public void set(int fromIndex, int toIndex, boolean value)
  • set(int bitIndex)

源碼如下:

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();
}

輸入參數 bitIndex 表示位集下標

該函數功能是設定位集中下標爲 bitIndex 的值爲 true

首先判斷下標 bitIndex 是否大於 0,如果不是,拋出 IndexOutOfBoundsException 異常

然後定義變量 wordIndex,調用函數 wordIndex(bitIndex) 計算數組 words 中相對應的下標

調用函數 expandTo,輸入 wordIndex,確保 words[wordIndex] 不爲 null

進行位與操作,設定位集中該下標的值爲 true

調用函數 checkInvariants() 檢查

  • set(int bitIndex, boolean value)

源碼如下:

public void set(int bitIndex, boolean value) {
    if (value)
        set(bitIndex);
    else
        clear(bitIndex);
}

該函數可以用值 value 設定位集中下標爲 bitIndex 的位

如果 value = true,調用方法 set(int bitIndex)

如果 value = false,調用方法 clear(bitIndex) 來清除該位值

  • set(int fromIndex, int toIndex)

源碼如下:

public void set(int fromIndex, int toIndex) {
    checkRange(fromIndex, toIndex);

    if (fromIndex == toIndex)
        return;

    // Increase capacity if necessary
    int startWordIndex = wordIndex(fromIndex);
    int endWordIndex   = wordIndex(toIndex - 1);
    expandTo(endWordIndex);

    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] = WORD_MASK;

        // Handle last word (restores invariants)
        words[endWordIndex] |= lastWordMask;
    }

    checkInvariants();
}

該函數用於設置位集中範圍在 [fromIndex, toIndex) 之間的值爲 true

Note:具體設置的位集範圍應該是 [fromIndex, toIndex-1]

第一步:調用函數 checkRange 判斷輸入參數 fromIndextoIndex 是否符合標準

第二步:如果兩個參數相等,則返回

第三步:調用函數 wordIndex,將位集下標轉換爲數組下標,得到變量 startWordIndexendWordIndex;同時調用函數 expandTo,確保數組容量

第四步:計算掩碼 firstWordMasklastWordMask;如果設置範圍在數組 words 的同一個數中,則利用掩碼對該數進行位與操作;如果不在數組 words 的同一個數中,那麼對多個數進行位與操作

最後調用函數 checkInvariants 檢查類變量是否符合條件

  • set(int fromIndex, int toIndex, boolean value)

源碼如下:

public void set(int fromIndex, int toIndex, boolean value) {
    if (value)
        set(fromIndex, toIndex);
    else
        clear(fromIndex, toIndex);
}

同樣可以在批量設置中設定具體值

expandTo

源碼如下:

private void expandTo(int wordIndex) {
    int wordsRequired = wordIndex+1;
    if (wordsInUse < wordsRequired) {
        ensureCapacity(wordsRequired);
        wordsInUse = wordsRequired;
    }
}

在使用過程中,位集的大小可以按需要增長。爲防止輸入位集下標超出當前位集,可以調用 expandTo 來擴展數組 words 的大小

Note:輸入參數 wordIndex 是數組下標

首先定義變量 wordsRequired 計算需要的最小的數組大小

如果當前數組中已使用的數量 wordsInUse 小於需要的大小,那麼可以調用函數 ensureCapacity 確保數組 words 足夠,同時設置 wordsInUse 的最新值爲 `wordsRequired“

ensureCapacity

源碼如下:

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

輸入參數 wordsRequired 表示當前所需的數組大小

此函數功能是確保數組 words 能夠滿足需求

如果當前數組 words 長度小於 wordsRequired,那麼定義變量 request 計算新建數組大小(兩倍當前數組長度或者請求的數組大小,取其中最大值),調用函數 Arrays.copyOf 擴展數組 words,同時設置類變量 sizeIsStickyfalse

checkInvariants

源碼如下:

private void checkInvariants() {
    assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
    assert(wordsInUse >= 0 && wordsInUse <= words.length);
    assert(wordsInUse == words.length || words[wordsInUse] == 0);
}

公共方法改變類 BitSet 實例變量後,調用此函數保證類變量符合以下條件

checkRange

源碼如下:

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);
}

檢查輸入參數 fromIndextoIndex 是否符合條件,即 0 < fromIndex < toIndex,如果不符合,則拋出 IndexOutOfBoundsException 異常

實例測試

下面輸入具體參數來驗證函數 set(int fromIndex, int toIndex) 的計算過程(假設當前對象位集均爲 false

輸入參數 fromIndex = 35, toIndex = 148,所以要設置位集範圍 [35, 148) 的位值爲 true

0 < fromIndex < toIndex,所以符合函數 checkRange 判斷條件

變量 startWordIndex = 0, endWordIndex = 2

firstWordMask = WORD_MASK << fromIndex = 0xffffffffffffffffL << 0010 0011(取後 6 位) = WORD_MASK << 35 = 0xfffffff800000000L

lastWordMask = WORD_MASK >>> -toIndex = 0xffffffffffffffffL >>> 1110 1100(取後 6 位) = WORD_MASK >>> 44 = 0x00000000000fffffL

因爲 startWordIndex != endWordIndex,所以先計算數組 words 下標 0 的位與操作

words[startWordIndex] |= firstWordMask -> 0x0000000000000000L |= 0xfffffff800000000L = 0xfffffff800000000L

再計算數組下標 1 的位與操作

words[1] |= WORD_MASK -> 0x0000000000000000L |= 0xffffffffffffffffL = 0xffffffffffffffffL

最後計算數組下標 2 的位與操作

words[2] |= lastWordMask -> 0x0000000000000000L |= 0x00000000000fffffL = 0x00000000000fffffL

測試代碼如下:

public static void main(String args[]) {
    BitSet bitSet = new BitSet();

    bitSet.set(35, 148);
    System.out.println(bitSet.cardinality());

    long[] res = bitSet.toLongArray();
    for (Long l : res) {
        System.out.println(Long.toBinaryString(l));
    }
}

這裏寫圖片描述

取值範圍長度爲 toIndex - fromIndex = 148 - 35 = 113

發佈了335 篇原創文章 · 獲贊 275 · 訪問量 169萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章