java.util.BitSet
类实现了一个根据需要增长的位向量。它的核心思想是将一系列的布尔值(位)紧凑地存储在一个 long
类型的数组中,每个 long
值可以存储 64 个位。
Java的BitSet没有使用 isFlipped标记,因此flip和count的复杂度不是O(1)的,但这使得实现变得简单和清晰。
类结构和关键字段
BitSet.java
java
public class BitSet implements Cloneable, java.io.Serializable {
/*
* 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 static final int ADDRESS_BITS_PER_WORD = 6; // 每个word的地址位数 (2^6 = 64)
private static final int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; // 每个word的位数 (64)
private static final int BIT_INDEX_MASK = BITS_PER_WORD - 1; // 用于获取位在word内的索引的掩码 (63, 即 0x3F)
/* Used to shift left or right for a partial word mask */
private static final long WORD_MASK = 0xffffffffffffffffL; // 全1的掩码,用于操作整个word
/**
* @serialField bits long[]
*
* The bits in this BitSet. The ith bit is stored in bits[i/64] at
* bit position i % 64 (where bit position 0 refers to the least
* significant bit and 63 refers to the most significant bit).
*/
@java.io.Serial
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("bits", long[].class),
};
/**
* The internal field corresponding to the serialField "bits".
*/
private long[] words; // 存储位的long数组
/**
* The number of words in the logical size of this BitSet.
*/
private transient int wordsInUse = 0; // 实际使用的word数量,表示BitSet的逻辑大小
/**
* Whether the size of "words" is user-specified. If so, we assume
* the user knows what he's doing and try harder to preserve it.
*/
private transient boolean sizeIsSticky = false; //标记words数组的大小是否由用户指定
/* use serialVersionUID from JDK 1.0.2 for interoperability */
@java.io.Serial
private static final long serialVersionUID = 7997698588986878753L;
// ... 其他方法和字段 ...
}
关键点解释:
words
(long[]) : 这是BitSet
内部存储数据的核心。每个long
元素被称为一个 "word",可以存储 64 个位。第i
个位存储在words[i/64]
的第i % 64
个比特位上(最低有效位是第0位)。ADDRESS_BITS_PER_WORD
(int) : 固定为 6。因为一个long
是 64 位,而 2^6 = 64,所以需要 6 位来寻址一个long
中的具体某一位。BITS_PER_WORD
(int) : 固定为 64,即一个long
包含的位数。BIT_INDEX_MASK
(int) : 值为 63 (二进制00111111
)。用于通过位与操作 (& BIT_INDEX_MASK
) 快速计算一个位索引在其所属long
word 内的偏移量(相当于bitIndex % 64
)。WORD_MASK
(long) : 值为0xffffffffffffffffL
(全1)。用于对整个long
word 进行操作,例如全部置位或全部清零。wordsInUse
(transient int) : 表示words
数组中当前实际被用来存储有效位的long
元素的数量。这是BitSet
的逻辑大小。例如,如果最高的被设置的位是第 100 位,那么wordsInUse
至少是wordIndex(100) + 1
。这个字段是transient
的,因为它可以在序列化和反序列化时重新计算。sizeIsSticky
(transient boolean) : 如果用户在构造BitSet
时指定了初始大小 (nbits
),这个标志位会设为true
。在这种情况下,BitSet
会尽量保持words
数组的大小,避免不必要的缩小。serialPersistentFields
: 定义了BitSet
对象在序列化时需要持久化的字段,这里只有bits
(即words
数组)。
构造函数
-
public BitSet()
:BitSet.java
javapublic BitSet() { initWords(BITS_PER_WORD); // 初始化words数组,默认大小为一个word (64位) sizeIsSticky = false; }
默认构造函数创建一个初始容量为 64 位的
BitSet
。 -
public BitSet(int nbits)
:BitSet.java
javapublic BitSet(int nbits) { // nbits can't be negative; size 0 is OK if (nbits < 0) throw new NegativeArraySizeException("nbits < 0: " + nbits); initWords(nbits); // 根据nbits初始化words数组 sizeIsSticky = true; // 用户指定了大小 }
允许用户指定初始可以表示的位数。
initWords
会计算需要多少个long
来存储nbits
。 -
private void initWords(int nbits)
:BitSet.java
javaprivate void initWords(int nbits) { words = new long[wordIndex(nbits-1) + 1]; }
这个方法根据需要的位数
nbits
来初始化words
数组。wordIndex(nbits-1)
计算出存储第nbits-1
位所需要的 word 的索引,然后加 1 得到数组的长度。如果nbits
为 0,nbits-1
为 -1,wordIndex(-1)
会根据右移操作的特性得到 -1,所以words
数组长度为 0。
wordIndex
-
private static int wordIndex(int bitIndex)
:BitSet.java
javaprivate static int wordIndex(int bitIndex) { return bitIndex >> ADDRESS_BITS_PER_WORD; // 相当于 bitIndex / 64 }
计算给定
bitIndex
所在的long
word 在words
数组中的索引。通过右移ADDRESS_BITS_PER_WORD
(即 6) 位实现,这等价于除以 64。
容量管理
-
private void ensureCapacity(int wordsRequired)
:BitSet.java
javaprivate 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; // 自动扩展后,不再认为是用户指定大小 } }
当需要的
long
word 数量 (wordsRequired
) 超过当前words
数组的长度时,此方法会扩展数组。新的大小通常是当前大小的两倍和wordsRequired
中的较大者。 -
private void expandTo(int wordIndex)
:javaprivate void expandTo(int wordIndex) { int wordsRequired = wordIndex+1; if (wordsInUse < wordsRequired) { ensureCapacity(wordsRequired); // 确保容量足够 wordsInUse = wordsRequired; // 更新wordsInUse } }
确保
BitSet
至少可以容纳到wordIndex
所指定的 word。如果当前的wordsInUse
小于需要的wordsRequired
,它会调用ensureCapacity
来扩展words
数组,并更新wordsInUse
。这个方法可能会临时破坏不变性条件(例如,新扩展的 word 可能为0,但wordsInUse
已经指向它),调用者需要负责恢复。
维护 wordsInUse
和不变性
-
private void recalculateWordsInUse()
:BitSet.java
javaprivate void recalculateWordsInUse() { // Traverse the bitset until a used word is found int i; for (i = wordsInUse-1; i >= 0; i--) if (words[i] != 0) break; wordsInUse = i+1; // The new logical size }
当某些操作(如
clear
或flip
)可能导致最高的置位被清除时,调用此方法。它从wordsInUse-1
开始向前遍历words
数组,找到最后一个不为0的 word,然后更新wordsInUse
为该 word 的索引加1。 -
private void checkInvariants()
:BitSet.java
javaprivate void checkInvariants() { assert(wordsInUse == 0 || words[wordsInUse - 1] != 0); // 如果wordsInUse > 0,最后一个使用的word必须非0 assert(wordsInUse >= 0 && wordsInUse <= words.length); // wordsInUse必须在有效范围内 assert(wordsInUse == words.length || words[wordsInUse] == 0); // 如果wordsInUse < words.length,则wordsInUse位置的word必须为0 }
这是一个断言方法,用于在开发和测试期间检查
BitSet
内部状态的一致性。- 第一个断言确保如果
wordsInUse
大于0,那么words[wordsInUse - 1]
(即逻辑上的最后一个word) 必须是非零的。 - 第二个断言确保
wordsInUse
不会超出words
数组的边界。 - 第三个断言确保如果
wordsInUse
没有达到words
数组的末尾,那么words[wordsInUse]
(即逻辑大小之外的第一个word) 必须是0。
- 第一个断言确保如果
核心位操作方法
-
public void set(int bitIndex)
:BitSet.java
javapublic void set(int bitIndex) { if (bitIndex < 0) throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); int wordIndex = wordIndex(bitIndex); expandTo(wordIndex); // 确保能容纳该位 words[wordIndex] |= (1L << bitIndex); // 使用位或操作设置位 checkInvariants(); // 检查不变性 }
将指定索引的位设置为
true
。- 计算
wordIndex
。 - 调用
expandTo(wordIndex)
确保words
数组足够大,并且wordsInUse
被相应更新。 - 通过位或操作
|=
将words[wordIndex]
中对应bitIndex % 64
的位设置为1。表达式(1L << bitIndex)
会自动处理bitIndex % 64
,因为long
左移超过63位时,实际移位数是bitIndex % 64
。
- 计算
-
public void clear(int bitIndex)
:BitSet.java
javapublic void clear(int bitIndex) { if (bitIndex < 0) throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); int wordIndex = wordIndex(bitIndex); if (wordIndex >= wordsInUse) // 如果位超出了当前逻辑大小,则它已经是false,无需操作 return; words[wordIndex] &= ~(1L << bitIndex); // 使用位与非操作清除位 recalculateWordsInUse(); // 可能清除了最高的置位,需要重新计算wordsInUse checkInvariants(); }
将指定索引的位设置为
false
。- 如果
wordIndex
超出wordsInUse
,说明该位已经是false
,直接返回。 - 通过位与非操作
&= ~
将words[wordIndex]
中对应bitIndex % 64
的位设置为0。 - 调用
recalculateWordsInUse()
,因为这个操作可能清除了最高的有效位。
- 如果
-
public void flip(int bitIndex)
:BitSet.java
javapublic 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(); }
翻转指定索引的位的值。
- 计算
wordIndex
并调用expandTo(wordIndex)
。 - 通过位异或操作
^=
翻转words[wordIndex]
中对应bitIndex % 64
的位。 - 调用
recalculateWordsInUse()
。
- 计算
-
public boolean get(int bitIndex)
:javapublic boolean get(int bitIndex) { if (bitIndex < 0) throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex); checkInvariants(); int wordIndex = wordIndex(bitIndex); return (wordIndex < wordsInUse) // 必须在逻辑大小内 && ((words[wordIndex] & (1L << bitIndex)) != 0); // 检查对应位是否为1 }
获取指定索引的位的值。
- 如果
wordIndex
超出wordsInUse
,则该位为false
。 - 否则,通过位与操作
&
检查words[wordIndex]
中对应bitIndex % 64
的位是否为1。
- 如果
范围操作 (例如 set(int fromIndex, int toIndex)
)
这类方法会处理跨越多个 long
word 的情况。它们通常会:
- 计算起始
wordIndex
(startWordIndex
) 和结束wordIndex
(endWordIndex
)。 - 调用
expandTo(endWordIndex)
确保容量。 - 分别处理三种情况:
- 单个 word :
startWordIndex == endWordIndex
。操作只涉及一个long
。会创建一个掩码,只影响范围内的位。 - 多个 words :
- 第一个 word : 创建一个掩码,操作从
fromIndex % 64
到该 word 末尾的位。 - 中间的 words : 如果有,这些 words 会被整个设置为目标值 (例如,
WORD_MASK
表示全1,0 表示全0)。 - 最后一个 word : 创建一个掩码,操作从该 word 开头到
(toIndex - 1) % 64
的位。
- 第一个 word : 创建一个掩码,操作从
- 单个 word :
例如 set(int fromIndex, int toIndex)
:
BitSet.java
java
// ...
long firstWordMask = WORD_MASK << fromIndex; // 保留 fromIndex 右边的位 (包括 fromIndex)
long lastWordMask = WORD_MASK >>> -toIndex; // 保留 toIndex 左边的位 (不包括 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; // 全部设为1
// Handle last word (restores invariants)
words[endWordIndex] |= lastWordMask;
}
// ...
WORD_MASK << fromIndex
(等价于 WORD_MASK << (fromIndex % 64)
) 会生成一个掩码,其中从 fromIndex % 64
位开始到最高位都是1,其余是0。
WORD_MASK >>> -toIndex
(等价于 WORD_MASK >>> (64 - (toIndex % 64))
当 toIndex % 64 != 0
时,或者 0
当 toIndex % 64 == 0
时) 会生成一个掩码,其中从最低位到 (toIndex - 1) % 64
位是1,其余是0。 这两个掩码结合起来用于精确操作范围内的位。
转换方法
-
public static BitSet valueOf(long[] longs)
/valueOf(ByteBuffer bb)
/valueOf(byte[] bytes)
: 这些静态工厂方法用于从long
数组、ByteBuffer
或byte
数组创建BitSet
。它们通常处理小端字节序(LITTLE_ENDIAN)。valueOf(long[])
会复制数组并去除末尾的0值word。valueOf(ByteBuffer)
和valueOf(byte[])
会将字节数据转换为long
word。 -
public byte[] toByteArray()
: 将BitSet
转换为byte
数组,同样采用小端字节序。它会计算实际需要的字节数,避免末尾不必要的0字节。 -
public long[] toLongArray()
: 将BitSet
的内部words
数组(只包括wordsInUse
部分)复制并返回。
valueOf(ByteBuffer bb)
- 从 ByteBuffer 构建 BitSet
这个静态工厂方法允许你从一个 ByteBuffer
对象中创建 BitSet
。这在需要从外部数据源(如文件、网络流)解析位集时非常有用。
实现分析:
java
public static BitSet valueOf(ByteBuffer bb) {
// 1. 创建缓冲区的切片,并设置为小端字节序 (LITTLE_ENDIAN)
// BitSet 内部处理字节到 long 的转换时,通常遵循小端模式,
// 即一个 long 中的最低有效字节先出现。
bb = bb.slice().order(ByteOrder.LITTLE_ENDIAN);
int n;
// 2. 从后向前找到第一个非零的字节,确定实际有效的字节数 n。
// 这是为了去除尾部的全零字节,优化存储。
for (n = bb.remaining(); n > 0 && bb.get(n - 1) == 0; n--)
;
// 3. 根据有效字节数 n 计算所需的 long 数组大小。
// 每个 long 可以存 8 个字节 (64位)。
long[] words = new long[(n + 7) / 8];
bb.limit(n); // 限制 ByteBuffer 的读取范围到有效字节。
int i = 0;
// 4. 如果剩余字节数足够组成一个或多个完整的 long (8字节),则直接读取。
while (bb.remaining() >= 8)
words[i++] = bb.getLong();
// 5. 处理剩余不足 8 字节的部分。
// 逐个字节读取,并按照小端序组合成最后一个 long。
for (int remaining = bb.remaining(), j = 0; j < remaining; j++)
words[i] |= (bb.get() & 0xffL) << (8 * j);
// 6. 使用构造的 long 数组创建新的 BitSet 实例。
return new BitSet(words);
}
有意思的点:
- 字节序处理 : 明确指定
ByteOrder.LITTLE_ENDIAN
,确保了跨平台和与其他系统交互时数据解释的一致性。 - 效率优化 : 通过
bb.slice()
避免修改原始 buffer 状态,通过预先计算有效字节数n
和bb.limit(n)
减少不必要的处理。 - 分段处理 : 先批量读取完整的
long
,再细致处理尾部不足一个long
的字节,兼顾了效率和正确性。
toByteArray()
- 将 BitSet 转换为 byte 数组
这个方法是 valueOf(byte[] bytes)
的逆操作,将 BitSet
的内容序列化为一个字节数组,同样采用小端字节序。
实现分析
BitSet.java
java
public byte[] toByteArray() {
int n = wordsInUse;
// 1. 如果 BitSet 为空,返回空字节数组。
if (n == 0)
return new byte[0];
// 2. 计算输出字节数组的长度。
// 首先假设除了最后一个 word 外,其他 word 都占满 8 字节。
int len = 8 * (n - 1);
// 然后计算最后一个 word (words[n-1]) 实际占用的字节数。
// 通过不断右移8位并检查是否为0来确定。
for (long x = words[n - 1]; x != 0; x >>>= 8)
len++;
byte[] bytes = new byte[len];
// 3. 使用 ByteBuffer 辅助写入,并设置为小端字节序。
ByteBuffer bb = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
// 4. 写入 n-1 个完整的 long。
for (int i = 0; i < n - 1; i++)
bb.putLong(words[i]);
// 5. 逐字节写入最后一个 long (words[n-1]) 的有效部分。
for (long x = words[n - 1]; x != 0; x >>>= 8)
bb.put((byte) (x & 0xff));
return bytes;
}
有意思的点:
- 精确长度计算 : 准确计算输出字节数组的长度,避免浪费空间,特别是对于最后一个
long
中未完全使用的部分。 - ByteBuffer 的使用 : 再次利用
ByteBuffer
及其字节序设置,简化了long
到字节序列的转换。
nextSetBit(int fromIndex)
-查找下一个为 true 的位
这个方法从指定的索引 fromIndex
(包含) 开始,查找并返回第一个值为 true
的位的索引。如果找不到,则返回 -1
。这对于遍历 BitSet
中所有设置的位非常有用。
实现分析:
java
public int nextSetBit(int fromIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
checkInvariants(); // 内部一致性检查
// 1. 计算 fromIndex 所在的 word 的索引 u。
int u = wordIndex(fromIndex);
// 2. 如果 u 超出了实际使用的 word 范围,说明后面没有置位的 bit 了。
if (u >= wordsInUse)
return -1;
// 3. 处理第一个 word (words[u])。
// 先获取 words[u] 的内容,然后清除掉 fromIndex % 64 之前的所有位。
// 例如,如果 fromIndex 是 65 (在第1个word的第1个bit),
// 那么 fromIndex % 64 是 1。 (1L << fromIndex) 会产生一个掩码,
// 这里实际上是想从 fromIndex 指定的 bit 开始检查。
// word = words[u] & (WORD_MASK << fromIndex) 的含义是:
// WORD_MASK 是全1 (0xff...ffL)。
// (WORD_MASK << fromIndex) 实际上是 (WORD_MASK << (fromIndex % 64))
// 它会生成一个掩码,其中 fromIndex % 64 位以及其后的位为1,之前的位为0。
// 所以 word 中只保留了从 fromIndex 开始的那些位。
long word = words[u] & (WORD_MASK << fromIndex);
while (true) {
// 4. 如果当前处理的 word 不为0,说明在这个 word 中找到了置位的 bit。
if (word != 0)
// Long.numberOfTrailingZeros(word) 返回 word 中最低位1后面的0的个数。
// (u * BITS_PER_WORD) 是当前 word 的起始 bit 索引。
// 两者相加即为全局的 bit 索引。
return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word);
// 5. 如果当前 word 为0,或者处理完第一个 word 后,移动到下一个 word。
u++;
// 6. 如果已经检查完所有使用的 word,则返回 -1。
if (u == wordsInUse)
return -1;
// 7. 获取下一个 word 的内容。
word = words[u];
}
}
有意思的点:
- 位操作的巧妙运用 :
(WORD_MASK << fromIndex)
(应理解为WORD_MASK << (fromIndex % ADDRESS_BITS_PER_WORD)
) 用于快速屏蔽掉一个long
中不需要检查的前缀位。 Long.numberOfTrailingZeros()
: 高效地找到一个long
中最低位的1
。这是 HotSpot VM 的一个 intrinsic 方法,性能很高。- 分段查找 : 先在起始
word
内查找,然后逐个word
查找,逻辑清晰。
and(BitSet set)
- 执行逻辑与操作
这个方法对当前的 BitSet
和传入的 BitSet
执行按位与(AND)操作,结果存储在当前的 BitSet
中。
实现分析
BitSet.java
java
public void and(BitSet set) {
// 1. 如果参数是自身,无需操作。
if (this == set)
return;
// 2. 清理 words 数组,确保 wordsInUse 是准确的。
// 如果 wordsInUse 比实际的 words 数组长度小,
// 且 wordsInUse 位置的 word 为0,则 wordsInUse 可能不是最新的。
// 这里通过将 this.wordsInUse 之后、set.wordsInUse 之前的 words[i] 清零,
// 确保后续的 recalculateWordsInUse 能正确工作。
while (wordsInUse > set.wordsInUse)
words[--wordsInUse] = 0;
// 3. 遍历两个 BitSet 中 words 数组的共同部分,执行按位与。
for (int i = 0; i < wordsInUse; i++)
words[i] &= set.words[i];
// 4. 重新计算当前 BitSet 的 wordsInUse,因为某些 word 可能在 AND 操作后变为0。
recalculateWordsInUse();
checkInvariants(); // 内部一致性检查
}
有意思的点:
- 就地修改 : 操作结果直接修改当前
BitSet
实例。 - 长度对齐 : 操作前,通过
while (wordsInUse > set.wordsInUse)
将当前BitSet
的逻辑长度调整得不超过参数BitSet
的逻辑长度(对于AND
操作,超出部分的结果必然是0)。 recalculateWordsInUse()
: 在位操作后,wordsInUse
可能需要更新,因为一些高位的long
可能变成了0。这个辅助方法会从后向前检查words
数组,找到最后一个非零的long
来确定新的wordsInUse
。
此外还有 or,xor,这两个方法基本类似,但是对于不同长度还需要设置剩余长度的位。
cardinality()
- 计算置位(为 true)的位数
这个方法返回 BitSet
中值为 true
的位的总数。
实现分析
BitSet.java
java
public int cardinality() {
int sum = 0;
// 遍历所有实际在使用的 words
for (int i = 0; i < wordsInUse; i++)
// Long.bitCount(long i) 是一个高效的内置方法,
// 用于计算一个 long 值中二进制表示下 '1' 的个数。
sum += Long.bitCount(words[i]);
return sum;
}
有意思的点:
Long.bitCount()
: 再次利用了Long
类的内置高效方法。这个方法通常由 JVM intrinsics 实现,速度非常快,远比自己循环判断每一位要高效。
get(int fromIndex, int toIndex)
- 获取指定范围的子 BitSet
这个方法创建一个新的 BitSet
,其内容是当前 BitSet
从 fromIndex
(包含) 到 toIndex
(不包含) 范围内的位。
实现分析
BitSet.java
java
public BitSet get(int fromIndex, int toIndex) {
checkRange(fromIndex, toIndex); // 检查范围有效性
checkInvariants(); // 内部一致性检查
int len = length(); // 获取当前 BitSet 的逻辑长度
// 1. 如果指定范围没有置位,或者范围为空,返回一个空的 BitSet。
if (len <= fromIndex || fromIndex == toIndex)
return new BitSet(0);
// 2. 优化:如果 toIndex 超出实际长度,将其修正为实际长度。
if (toIndex > len)
toIndex = len;
// 3. 创建结果 BitSet,大小为 toIndex - fromIndex。
BitSet result = new BitSet(toIndex - fromIndex);
// 4. 计算结果 BitSet 需要的 word 数量。
int targetWords = wordIndex(toIndex - fromIndex - 1) + 1;
// 5. 计算源 BitSet 开始读取的 word 索引。
int sourceIndex = wordIndex(fromIndex);
// 6. 判断源范围的起始位是否是 word 对齐的 (即 fromIndex % 64 == 0)。
boolean wordAligned = ((fromIndex & BIT_INDEX_MASK) == 0);
// 7. 处理所有完整的 word(除了可能跨越边界的最后一个 word)。
for (int i = 0; i < targetWords - 1; i++, sourceIndex++)
result.words[i] = wordAligned ? words[sourceIndex] :
// 如果未对齐,需要从 sourceIndex 和 sourceIndex+1 两个 word 中拼接。
// (words[sourceIndex] >>> fromIndex) 获取 sourceIndex 中 relevant部分
// (words[sourceIndex+1] << -fromIndex) 获取 sourceIndex+1 中 relevant部分
// (注意: -fromIndex 在这里实际上是 64 - (fromIndex % 64))
(words[sourceIndex] >>> fromIndex) |
(words[sourceIndex+1] << -fromIndex);
// 8. 处理最后一个 word。
// lastWordMask 用于屏蔽掉 toIndex 之后的不相关位。
long lastWordMask = WORD_MASK >>> -toIndex; // (注意: -toIndex 实际上是 64 - (toIndex % 64))
result.words[targetWords - 1] =
// 判断目标范围的结束位在源 word 内的偏移是否小于起始位在源 word 内的偏移
// 如果是,说明最后一个目标 word 跨越了两个源 word。
((toIndex-1) & BIT_INDEX_MASK) < (fromIndex & BIT_INDEX_MASK)
? /* straddles source words */
((words[sourceIndex] >>> fromIndex) |
(words[sourceIndex+1] & lastWordMask) << -fromIndex)
: /* within one source word */
((words[sourceIndex] & lastWordMask) >>> fromIndex);
// 9. 设置结果 BitSet 的 wordsInUse 并重新计算。
result.wordsInUse = targetWords;
result.recalculateWordsInUse();
result.checkInvariants();
return result;
}
有意思的点:
- 范围和对齐处理 : 这是
BitSet
中最复杂的操作之一。它需要精确处理源范围的起始和结束位可能不在long
(word) 边界上的情况。 - 位移操作的精妙 : 大量的位移操作 (
>>>
,<<
) 被用来从源words
中提取、组合所需的位到目标words
。words[sourceIndex] >>> fromIndex
:将源word
右移fromIndex % 64
位,使得需要的位移动到低位。words[sourceIndex+1] << -fromIndex
(等效于words[sourceIndex+1] << (64 - (fromIndex % 64))
):将下一个源word
的低位左移,以补充上一个word
右移后空出来的高位。
- 条件化的拼接逻辑 : 根据源范围是否对齐以及最后一个目标
word
是否跨越源word
边界,有不同的处理逻辑。
总结
BitSet
不是 线程安全的。如果多个线程并发修改一个 BitSet
实例而没有外部同步,可能会导致不确定的行为。
BitSet
通过使用 long
数组作为位存储的底层结构,实现了高效的空间利用和快速的位操作。它通过 wordsInUse
字段动态管理逻辑大小,并通过 ensureCapacity
和 expandTo
方法按需扩展物理存储。核心的位操作(set, clear, flip, get)都依赖于高效的位运算。范围操作则通过对起始、中间和结束 word 的分别处理来实现。