JDK源碼 - BitSet的實現

java.util.BitSet是個很有趣的類,瞭解其內部實現對正確的使用非常重要。

對象構造:

private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private long[] words;

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

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

public BitSet() {
initWords(BITS_PER_WORD);
...
}

public BitSet(int nbits) {
...
initWords(nbits);
...
}


從貼出來的代碼可以看出,long[] words這個數組是BitSet內部的關鍵實現,如果用戶在構造函數中輸入一個nbits變量,initWords方法會把這個數減1再右移6位加1,按照這個長度產生words數組的長度。
如果是輸入的28,那麼words的長度是1,
如果是輸入的2^6 = 64,那麼words的長度是1,
如果是輸入的2^6+1 = 65,那麼words的長度是2,
如果是輸入的(2^6)*2 = 128,那麼words的長度是2,
如果是輸入的(2^6)*2+1 = 129,那麼words的長度是3,
如果是輸入的(2^6)*3 = 192,那麼words的長度是3,
如果是輸入的(2^6)*3+1 = 193,那麼words的長度是4,
...

到這裏已經很清楚了,BitSet用long類型表示“位圖”,因爲一個long是64bit,所以每個long表示64個數據,也就是說:數組中words中的第一個long表示0~63,第二個long表示64~127,第三個long表示128~191 ...

再看看get函數,檢測某個數是否被置位:
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
...
int wordIndex = wordIndex(bitIndex);
return (wordIndex < wordsInUse)
&& ((words[wordIndex] & (1L << bitIndex)) != 0);
}

說明:
- wordsInUse變量主要用來控制long的容量,當set的數值過大時,BitSet類可以擴充words數組的長度,這一點和很多集合類(例如ArrayList,HashMap)是相似的
- 下面的語句值得注意:
1L << bitIndex
一般看到這條語句,會認爲bitIndex如果超過64位,高位會溢出並得到返回0,事實上這個1會重新循環到低位,也就是說:
1L << 64 返回爲1。
術語上這叫循環左移,經過檢測java同樣支持循環右移,運行下面的測試:
	for (int i = 0;i < 70 ;i++)
System.out.println(i + " =" + (1 >> i));

在i爲0,32,64時,(1 >> i)重新爲1,搞位運算編程的需要注意這個陷阱。

[color=red]注: 經過仔細考慮和試驗,這裏不是循環左移和循環右移,是一種比較“奇怪”的實現,回頭寫寫這個問題。[/color]

[b]BitSet類的一個缺陷:[/b]
size方法屬於類的內部實現細節,導出成公有方法會讓不瞭解實現細節的開發人員很迷惑。

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


例如: 開發人員可能通過bitset.set(100)設置位,然後調用bitset.size(),如果不瞭解細節,很難理解爲什麼結果爲128。

另外,如果實現位域(bit fields),應該考慮使用EnumSet,這一點可以參考Effective Java。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章