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) 快速计算一个位索引在其所属- longword 内的偏移量(相当于- bitIndex % 64)。
- WORD_MASK(long) : 值为- 0xffffffffffffffffL(全1)。用于对整个- longword 进行操作,例如全部置位或全部清零。
- 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所在的longword 在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; // 自动扩展后,不再认为是用户指定大小 } }当需要的 longword 数量 (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[])会将字节数据转换为longword。
- 
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 的分别处理来实现。