OpenJDK 源代碼閱讀之 BitSet

概要

  • 類繼承關係
java.lang.Object
    java.util.BitSet
  • 定義
public class BitSet
extends Object
implements Cloneable, Serializable
  • 要點

BitSet 類用來支持位操作,給它一個 size ,就會返回一個對象,代表 size 個位。可以完成“與或非”操作。

實現

試想一下,long 最多也就 64 位,假如我們想對 1000 位進行一些運算,要如何實現呢?這個類就告訴我們怎麼用一個數組,去實現位操作。

  • 數據
private long[] words;

內部使用 long 類型的數組來存儲數據。

  • 初始化
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;
}

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

private static int wordIndex(int bitIndex) {
    return bitIndex >> ADDRESS_BITS_PER_WORD;
}

初始化會根據的位數決定要申請多大的數組,long 類型是 64 位,所以你如果 nbits 是 1~64,你只需要一個長度爲1的數組就好。

  • 擴充策略

要是數組不夠用了,就要進行擴充,下面的函數會根據申請的 long 元素個數,經過與當前元素個數2倍的比較進行擴充。

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;
    }
}
  • 位翻轉
public void flip(int bitIndex) {
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

    int wordIndex = wordIndex(bitIndex);
    expandTo(wordIndex);

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

    recalculateWordsInUse();
    checkInvariants();
}

先根據索引位置 bitIndex 計算出相應的位在數組哪個元素裏,然後再將 1 左移 bitIndex 位後與此元素作異或運算。注意bitIndex 如果超過了 64 位,會又循環回來,比如 1L << 69 其實和 1L << 5 是一樣的,只不過異或的時候,一個與words[1] 異或,一個與 words[0]

類中還有其它位操作,比如置1,清0,只是和 flip 的位操作符不同。

還有一類是區間內翻轉,這需要首先臨到一個相應區間全爲1的數字,再與 words 相應元素作運算。

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

    if (fromIndex == toIndex)
        return;

    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
        words[endWordIndex] ^= lastWordMask;
    }

    recalculateWordsInUse();
    checkInvariants();
}

如果區間跨越多個數組元素,還需要把中間的數個數組元素內容全部翻轉。

  • AND 操作
public void and(BitSet set) {
    if (this == set)
        return;

    while (wordsInUse > set.wordsInUse)
        words[--wordsInUse] = 0;

    // Perform logical AND on words in common
    for (int i = 0; i < wordsInUse; i++)
        words[i] &= set.words[i];

    recalculateWordsInUse();
    checkInvariants();
}

從這個函數體會一下,兩個 BitSet 對象之間的 AND 操作如何進行,其實就是對應的數組元素之間作 AND 操作就行。

  • hashCode
public int hashCode() {
    long h = 1234;
    for (int i = wordsInUse; --i >= 0; )
        h ^= words[i] * (i + 1);

    return (int)((h >> 32) ^ h);
}

計算哈希值的操作,說實話,我是不太明白爲什麼這樣算哈希值的,爲什麼這樣能減少不同 BitSet 之間的碰撞呢?

剩下的東西我也不想分析了,總之,需要把握整體的思路,就是如何用一個數組去實現位操作,每次操作需要弄清楚,在數組的哪些元素上操作,與什麼數字作位操作,做什麼位操作。

如果對代碼有更多見解,可以在這個頁面添加註釋: rtfcode-BitSet

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