對象構造:
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。