BitMap源码解析

本文详细介绍了JDK中的BitSet数据结构,包括其工作原理、关键成员属性、添加和删除操作的实现、源码解析,以及与HashMap的区别。着重讨论了其在存储空间效率上的优点和在稀疏数据场景下的缺点,预告了RoaringBitmap的介绍。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

为什么称为bitmap?
bitmap不仅仅存储介质以及数据结构不同于hashmap,存储的key和value也不同。

bitmap的key是元素的index,value只有0或者1(具体结构见下文)。

数据结构

Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素。由于采用了Bit为单位来存储数据,因此可以很大程度上节省存储空间。

举例:
bitmap
key-value: bitmap[1] = 1、bitmap[2]=0

添加与删除操作

添加:使用1和key所在位的value进行 |(或)

删除:使用1和key所在位的value进行 &(与)

JDK中BitSet源码解析

位于java.util包中

重要成员属性

/*
 * 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.
 * 采用long作为载体,long有8个byte,所以有一个long有64个bit,64这个数字需要6个bit承载
 */
private final static int ADDRESS_BITS_PER_WORD = 6;
// 每一个words里面的元素占有64位
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;
/**
 * @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).
 */
private static final ObjectStreamField[] serialPersistentFields = {
    new ObjectStreamField("bits", long[].class),
};
/**
 * The internal field corresponding to the serialField "bits".
 * bitset的数据载体
 */
private long[] words;
/**
 * The number of words in the logical size of this BitSet.
 * 表示数组中最多使用的元素个数,也就是最后一个不为 0 的元素的索引加 1;比如[0,4,0,0],数组长度为 4,但是最后一个不为 0 的元素是 1,所以 wordsInUse = 2
 */
private transient int wordsInUse = 0;

初始化

创建一个 BitSet 对象时,默认 words 的长度为 1,并且 words[0] = 0。当然也可以用户给定一个具体的容量大小,如下代码:

/**
* BitSet.class
* 创建一个能存储给定数据索引的 BitSet
*/
public BitSet(int nbits) {
    // 参数合法性判断
    if (nbits < 0)
        throw new NegativeArraySizeException("nbits < 0: " + nbits);
    // 调用 initWords 方法初始化
    initWords(nbits);
    sizeIsSticky = true;
}

private void initWords(int nbits) {
    words = new long[wordIndex(nbits-1) + 1];
}
// 得到 bitIndex 对应的 words 下标
private static int wordIndex(int bitIndex) {
    return bitIndex >> ADDRESS_BITS_PER_WORD;
}

添加数据

public void set(int bitIndex) {
    // 参数合法性检验
    if (bitIndex < 0)
        throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
    // 得到对应的数组下标
    int wordIndex = wordIndex(bitIndex);
    // 是否要扩容
    expandTo(wordIndex);
    // 修改数据
    words[wordIndex] |= (1L << bitIndex); 
    // 参数检查
    checkInvariants();
}
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) {
            // Allocate larger of doubled size or required size
          	// 基本上是扩容两倍
            int request = Math.max(2 * words.length, wordsRequired);
            words = Arrays.copyOf(words, request);
            sizeIsSticky = false;
        }
    }

注意这里的set(bitIndex)是让二进制的位置为1,并不是让words数组的某一index为1.
扩容的逻辑是:如果需要的长度大于数组的两倍,则扩容到需要的长度。否则,扩容位数组的两倍。

清除数据

public void clear(int bitIndex) {
    //...
    int wordIndex = wordIndex(bitIndex);
    // 如果 wordIndex >= wordsInUse,说明该索引要么不存在,要么一定是 0 ,直接返回即可
    if (wordIndex >= wordsInUse)
        return;
    words[wordIndex] &= ~(1L << bitIndex);
    recalculateWordsInUse();
    //...
}
// 修改完可能会引起 wordsInUse 的变化,所以还要调用 recalculateWordsInUse() 重新计算 wordsInUse:从后往前遍历直到遇到 words[i] != 0,修改 wordsInUse = i+1。
private void recalculateWordsInUse() {
    int i;
    for (i = wordsInUse-1; i >= 0; i--)
        if (words[i] != 0)
            break;

    wordsInUse = i+1; // The new logical size
}

获取数据

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

size和length方法

/**
 * Returns the number of bits of space actually in use by this
 * {@code BitSet} to represent bit values.
 * The maximum element in the set is the size - 1st element.
 *
 * @return the number of bits currently in this bit set
 */
public int size() {
    return words.length * BITS_PER_WORD;
}

/**
 * Returns the "logical size" of this {@code BitSet}: the index of
 * the highest set bit in the {@code BitSet} plus one. Returns zero
 * if the {@code BitSet} contains no set bits.
 * 最高非0位+1
 *
 * @return the logical size of this {@code BitSet}
 * @since  1.2
 */
public int length() {
    if (wordsInUse == 0)
        return 0;
    return BITS_PER_WORD * (wordsInUse - 1) +
        (BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1]));
}
  • size方法:words数组的长度 * 64(每个long的长度)
  • lenght方法:最高位的1所在位置+ 1
    示例:
    示例

集合操作:与、或、异或

集合操作还是很常用的,具体不作说明了,自行去看源码。

优缺点

优点:可以大幅减少数据存储空间,适合稠密的数据场景
缺点:当数据稀散的时候,会浪费空间(例如存储1,1000000)

本文就到这里,为了解决普通bitmap的缺点,下一篇将介绍它的变体RoaringBitMap。

在C++的STL中实现由一个bitset类模板,其用法如下: std::bitset bs; 也就是说,这个bs只能支持64位以内的位存储和操作;bs一旦定义就不能动态增长了。本资源附件中实现了一个动态Bitset,和标准bitset兼容。 /** @defgroup Bitset Bitset位集类 * @{ */ //根据std::bitset改写,函数意义和std::bitset保持一致 class CORE_API Bitset: public Serializable { typedef typename uint32_t _Ty; static const int _Bitsperword = (CHAR_BIT * sizeof(_Ty)); _Ty * _Array; //最低位放在[0]位置,每位的默认值为0 int _Bits;//最大有效的Bit个数 private: int calculateWords()const; void tidy(_Ty _Wordval = 0); void trim(); _Ty getWord(size_t _Wpos)const; public: //默认构造 Bitset(); //传入最大的位数,每位默认是0 Bitset(int nBits); virtual ~Bitset(); //直接整数转化成二进制,赋值给Bitset,最高低放在[0]位置 Bitset(unsigned long long _Val); //拷贝构造函数 Bitset(const Bitset & b); Bitset(const char * str); Bitset(const std::string & str, size_t _Pos, size_t _Count); public: size_t size()const; //返回设置为1的位数 size_t count() const; bool subscript(size_t _Pos) const; bool get(size_t pos) const; //设置指定位置为0或1,true表示1,false表示0,如果pos大于数组长度,则自动扩展 void set(size_t _Pos, bool _Val = true); //将位数组转换成整数,最低位放在[0]位置 //例如数组中存放的1011,则返回13,而不是返回11 unsigned long long to_ullong() const; bool test(size_t _Pos) const; bool any() const; bool none() const; bool all() const; std::string to_string() const; public: //直接整数转化成二进制,赋值给Bitset,最高位放在[0]位置 Bitset& operator = (const Bitset& b); //直接整数转化成二进制,赋值给Bitset,最高位放在[0]位置 Bitset& operator = (unsigned long long ull); //返回指定位置的值,如果pos大于位数组长度,自动拓展 bool operator [] (const size_t pos); //测试两个Bitset是否相等 bool operator == (const Bitset & b); bool operator != (const Bitset & b); Bitset operator<>(size_t _Pos) const; bool operator > (const Bitset & c)const; bool operator < (const Bitset & c)const; Bitset& operator &=(const Bitset& _Right); Bitset& operator|=(const Bitset& _Right); Bitset& operator^=(const Bitset& _Right); Bitset& operator<>=(size_t _Pos); public: Bitset& flip(size_t _Pos); Bitset& flip(); //将高位与低位互相,如数组存放的是1011,则本函数执行后为1101 Bitset& reverse(); //返回左边n位,构成新的Bitset Bitset left(size_t n) const; //返回右边n位,构成新的Bitset Bitset right(size_t n) const; //判断b包含的位数组是否是本类的位数组的自串,如果是返回开始位置 size_t find (const Bitset & b) const; size_t find(unsigned long long & b) const; size_t find(const char * b) const; size_t find(const std::string & b) const; //判断本类的位数组是否是b的前缀 bool is_prefix(unsigned long long & b) const; bool is_prefix(const char * b) const; bool is_prefix(const std::string & b) const; bool is_prefix(const Bitset & b) const; void clear(); void resize(size_t newSize); void reset(const unsigned char * flags, size_t s); void reset(unsigned long long _Val); void reset(const char * _Str); void reset(const std::string & _Str, size_t _Pos, size_t _Count); //左移动n位,返回新的Bitset //extendBits=false "1101" 左移动2位 "0100"; //extendBits=true "1101" 左移动2位 "110100"; Bitset leftShift(size_t n,bool extendBits=false)const; //右移动n位,返回新的Bitset //extendBits=false "1101" 右移动2位 "0011"; //extendBits=true "1101" 右移动2位 "001101"; Bitset rightShift(size_t n, bool extendBits = false) const; public: virtual uint32_t getByteArraySize(); // returns the size of the required byte array. virtual void loadFromByteArray(const unsigned char * data); // load this object using the byte array. virtual void storeToByteArray(unsigned char ** data, uint32_t& length) ; // store this object in the byte array. };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

倜傥村的少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值