概要
- 類繼承關係
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