三种 Bitmap 之 Java BitSet

1、继承体系及属性

public class BitSet implements Cloneable, java.io.Serializable {
    // 保存bit的数组
    private long[] words;
    // 表示在数组words中已经使用的数的个数
    private transient int wordsInUse = 0; //用于判断检查,扩容等相关的参数
    // 保存bit的words数组的位数是否由用户指定
    private transient boolean sizeIsSticky = false;
    //long类型的地址位数,即进行操作时位移的个数
    private final static int ADDRESS_BITS_PER_WORD = 6; //1 << 6 = 64
    //表示位移的数字大小
    private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; //64
    private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1; //63
    //称为掩码,用于位移时进行运算
    private static final long WORD_MASK = 0xffffffffffffffffL; //-1L
}

2、构造方法及关联的方法

//默认构造方法,默认初始化数组容量为 1
public BitSet() {
    initWords(BITS_PER_WORD);
    sizeIsSticky = false;
}

//根据传入的参数初始化数组容量
public BitSet(int nbits) {
    // 位数不能是负数,但可以是0
    if (nbits < 0)
        throw new NegativeArraySizeException("nbits < 0: " + nbits);

    initWords(nbits);
    sizeIsSticky = true;
}

//long 数组的大小为:nbits / 64 + nbits % 64
private void initWords(int nbits) {
    words = new long[wordIndex(nbits-1) + 1];
}

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

initWordswordIndex 可作为数组切分的标准方法参考

3、set 方法

public void set(int bitIndex) {
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
    // 根据传入的位索引定位到long数组中哪一个元素
    int wordIndex = wordIndex(bitIndex);
    // 判断是否需要扩容
    expandTo(wordIndex);
	//进行位与运算,将定位到的 long 数组中的元素对应位置上的位设置为 1
    words[wordIndex] |= (1L << bitIndex);
    // 使用断言校验wordsInUse变量
    checkInvariants();
}

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

//wordIndex 为 long[] 的 index
private void expandTo(int wordIndex) {
    int wordsRequired = wordIndex + 1;
    if (wordsInUse < wordsRequired) {
        ensureCapacity(wordsRequired);
        wordsInUse = wordsRequired;
    }
}

private void ensureCapacity(int wordsRequired) {
    // 如果原数组长度小于实际数组所需容量,扩容
    if (words.length < wordsRequired) {
        // 扩容2倍或实际所需容量,取两者之间的最大值
        int request = Math.max(2 * words.length, wordsRequired);
        words = Arrays.copyOf(words, request);
        sizeIsSticky = false;
    }
}

public static long[] copyOf(long[] original, int newLength) {
	long[] copy = new long[newLength];
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

private void checkInvariants() {
	assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
    assert(wordsInUse >= 0 && wordsInUse <= words.length);
    assert(wordsInUse == words.length || words[wordsInUse] == 0);
}

大致步骤如下:

  1. 判断传入的参数位索引是否小于 0
  2. 根据传入的位索引定位到 long 数组中哪一个元素
  3. 根据 wordsInUse 变量判断 long 数组中实际所使用的元素个数是否小于实际所需要的个数
  4. 若是,再判断数组的长度是否小于实际所需要的数组容量
  5. 若小于,比较扩容两倍后的容量与实际所需要的容量,取两者之间的较大者进行扩容
  6. 进行位与运算,将定位到的 long 数组中的元素对应位置上的位设置为 1
  7. 断言校验 wordsInUse 变量,所有 public 方法都会调用

/**
 * 将指定位索引处的值设置为指定的值
 * 1. 若 value 是 true,设为 1
 * 2. 若为 false,设为 0
 */
public void set(int bitIndex, boolean value) {
    if (value)
        set(bitIndex);
    else
        // clear方法来清除对应索引处的值,也就是设置为0
        clear(bitIndex);
}

public void clear(int bitIndex) {
	if (bitIndex < 0)
    	throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

	int wordIndex = wordIndex(bitIndex);
    if (wordIndex >= wordsInUse)
    	return;

    words[wordIndex] &= ~(1L << bitIndex); //反、与运算,将指定位置设为 0

	recalculateWordsInUse();
    checkInvariants();
}

//校验 long[] 是否保存的值均有效(非 0)并更新 wordsInUse 的有效值
private 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
}

//设置指定范围内的位索引(不包含fromIndex,包含 toIndex)
public void set(int fromIndex, int toIndex) {
    // 1. 校验开始索引和结束索引是否合法,不合法抛异常
    checkRange(fromIndex, toIndex);
    // 2. 若两个索引相同,直接结束程序流程,不进行任何操作
    if (fromIndex == toIndex)
        return;
    // 3. 定位索引所在数组中的位置
    int startWordIndex = wordIndex(fromIndex);
    int endWordIndex   = wordIndex(toIndex - 1);
    // 确认是否扩容
    expandTo(endWordIndex);
    // 计算掩码
    long firstWordMask = WORD_MASK << fromIndex;
    long lastWordMask  = WORD_MASK >>> -toIndex;
    // 如果定位的索引在数组的同一个元素中
    if (startWordIndex == endWordIndex) {
        // 先对firstWordMask和lastWordMask进行与运算,再进行位或运算(补码运算)
        words[startWordIndex] |= (firstWordMask & lastWordMask);
    } else {
        // 如果不在数组的同一个元素中
        // 对第一个元素进行位于操作,将对应索引的位设置为1
        words[startWordIndex] |= firstWordMask;
        // 如果元素有多个,全部置为1
        for (int i = startWordIndex+1; i < endWordIndex; i++)
            words[i] = WORD_MASK;
        // 对最后一个元素也进行位于操作,将对应索引的位设置为1
        words[endWordIndex] |= lastWordMask;
    }
    checkInvariants();
}

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);
}

4、get 方法

public boolean get(int bitIndex) {
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
    checkInvariants();
    // 定位到具体的long类型
    int wordIndex = wordIndex(bitIndex);
    // 对应索引位上进行位与操作,判断是否等于0来返回对应true和false
    return (wordIndex < wordsInUse) && ((words[wordIndex] & (1L << bitIndex)) != 0);
}

5、flip 方法

//反转某一个索引位,即将 1 置为 0,将 0 置为 1
public 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();
}

6、nextSetBit 方法

//获取指定索引处及之后第一个 1 的位置
public int nextSetBit(int fromIndex) {
    if (fromIndex < 0)
        throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
    checkInvariants();
    int u = wordIndex(fromIndex);
    if (u >= wordsInUse)
        return -1;
    
    // 获取word的实际值
    long word = words[u] & (WORD_MASK << fromIndex);

    while (true) {
        if (word != 0)
            return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word);
        if (++u == wordsInUse)
            return -1;
        word = words[u];
    }
}

该方法用于遍历 BitSet:

for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
    if (i == Integer.MAX_VALUE) {
        break;
    }
}

7、size,length,cardinality 方法

//返回数组大小与 64 的乘积
public int size() {
    return words.length * BITS_PER_WORD;
}

//返回 BitSet 的逻辑大小
public int length() {
    if (wordsInUse == 0)
        return 0;

    return BITS_PER_WORD * (wordsInUse - 1) 
    		+ (BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1]));
}

//返回 BitSet 中实际元素的个数
public int cardinality() {
    int sum = 0;
    for (int i = 0; i < wordsInUse; i++)
        sum += Long.bitCount(words[i]);
    return sum;
}

public static int numberOfLeadingZeros(long i) {
	if (i == 0)
    	return 64;
    int n = 1;
    int x = (int)(i >>> 32);
    if (x == 0) { n += 32; x = (int)i; }
    if (x >>> 16 == 0) { n += 16; x <<= 16; }
    if (x >>> 24 == 0) { n +=  8; x <<=  8; }
    if (x >>> 28 == 0) { n +=  4; x <<=  4; }
    if (x >>> 30 == 0) { n +=  2; x <<=  2; }
    n -= x >>> 31;
    return n;
}

public static int bitCount(long i) {
	i = i - ((i >>> 1) & 0x5555555555555555L);
    i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L);
    i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL;
    i = i + (i >>> 8);
    i = i + (i >>> 16);
    i = i + (i >>> 32);
    return (int)i & 0x7f;
}

注意:BitSet 可以动态扩容,但不可以动态减少,所以可能会出现 OutOfMemory

8、案例

//进行数字排序
public static void sortArray() {
    int[] array = new int[] { 423, 700, 9999, 2323, 356, 6400, 1,2,3,2,2,2,2 };
    BitSet bitSet = new BitSet(2 << 13);
    // 虽然可以自动扩容,但尽量在构造时指定估算大小,
    // 我们要看一下数组中的最大值,然后指定大致的容量,默认为64
    System.out.println("BitSet size: " + bitSet.size());

    for (int i = 0; i < array.length; i++) {
        bitSet.set(array[i]);
    }
    //剔除重复数字后的元素个数
    int bitLen=bitSet.cardinality();
    System.out.println("要排序的数组容量:" + array.length);
    System.out.println("剔除重复数字后的元素个数:" + bitLen);

    //进行排序,即把bit为true的元素复制到另一个数组
    int[] orderedArray = new int[bitLen];
    int k = 0;
    for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
        orderedArray[k++] = i;
    }

    System.out.println("After ordering: ");
    for (int i = 0; i < bitLen; i++) {
        System.out.print(orderedArray[i] + "\t");
    }
    System.out.println("\n通过迭代器获取排序后的元素:");
    //或直接迭代BitSet中bit为true的元素
    for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
        System.out.print(i+"\t");
    }
    System.out.println("\n---------------------------");
}

//结果:
BitSet size: 16384
要排序的数组容量:13
剔除重复数字后的元素个数:9
After ordering: 
1   2   3   356 423 700 2323    6400    9999    
通过迭代器获取排序后的元素:
1   2   3   356 423 700 2323    6400    9999    
---------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值