參考:
查看類 ArrayList
中 removeIf
方法源碼時,發現其使用 BitSet
類來存儲待刪除的元素下標
之前沒有接觸過這個類,瞭解之後發現其在數據查詢和存儲方面有很大用處
主要內容:
BitSet
淺析- 類變量和常量
- 構造器
set
clear
-(2)get
flip
- (3)valueOf
- 位運算(
and, andNot, or, xor
) next
previous
- (4)- 判空 / 判斷交集
- 大小(
length, size, cardinality
) toByteArray
和toLongArray
BitSet
分析Java
素數查找- 小結
BitSet
淺析
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
)
然後設置變量 sizeIsSticky
爲 false
,即用戶沒有設定數組 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
最後,設置 sizeIsSticky
爲 true
,表示用戶指定了數組 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
set
類 BitSet
提供了 4
個 set
函數:
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
判斷輸入參數 fromIndex
和 toIndex
是否符合標準
第二步:如果兩個參數相等,則返回
第三步:調用函數 wordIndex
,將位集下標轉換爲數組下標,得到變量 startWordIndex
和 endWordIndex
;同時調用函數 expandTo
,確保數組容量
第四步:計算掩碼 firstWordMask
和 lastWordMask
;如果設置範圍在數組 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
,同時設置類變量 sizeIsSticky
爲 false
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);
}
檢查輸入參數 fromIndex
和 toIndex
是否符合條件,即 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