java.utils.BitSet位存储

前言

计算机中一个字节(byte)占8位(bit),我们java数据至少按字节存储的,比如一个int占4个字节。
如果遇到大的数据量,这样必然会需要很大存储空间和内存。
如何减少数据占用存储空间和内存可以用算法解决。java.util.BitSet就提供了这样的算法。

简介

jdk的工具类中提供了BitSet位存储的工具类,通过位来存储数据,可大大节约存储空间,同时消耗更少的CPU运算来查找数据。

存储示例

比如我们现在要存储 [0,2,4,6,7,15]这个6个数字,如果采用int数组进行存放,那么要占用的字节数目4*6=24个字节,但如果是采用BitSet如下所示,只需2个字节即可:

存储前 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
存储后 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 1

内部实现

  • 初始化
    jdk中默认是通过创建long数组的方式来进行存储的,默认会创建size为4的words,也即是可以存储0~63共64个数字。通过提供的默认构造方法可以指定创建的初始大小。
public BitSet(int nbits);
//默认使用long数组来进行存储
private void initWords(int nbits) {
    words = new long[wordIndex(nbits-1) + 1];
}
  • 插入
    插入源码如下所示,根据代码可看出其步骤如下:
    1.获取插入元素指定的索引
    2.判断索引是否大于已用索引,如果大于则进行数组扩容
    3.执行插入
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
    }

在此我们创建BitSet然后依次插入[0,2,4,6],然后打断点看一下long数组存储了什么内容:
打印的内容如下:[85,0,0,0],可以发现,我们的四个元素只存储至了第一个long,即0~63以内的元素只能改变word数组的第一个元素。

如果我们此时直接插入10000,那么words数组会如何扩容呢? 10000 / 64 约等于 157,所以原有的长度为4的数组为扩容至157,且words[156]=65536。

根据上述描述,我们可以发现如果我们的数据离散型特别大,会导致BitSet创建过多的无用存储空间,所以BitSet比较适用于数据聚合性比较的场景。

如果是离散型比较大,但又想使用到BitSet的特性,可以考虑使用Google的EWAHCompressedBitmap,其对于这种场景做了专门的优化,可兴趣的可以参考 漫画:Bitmap算法 整合版
EWAHCompressedBitmap的依赖如下:



    com.googlecode.javaewah
    JavaEWAH
    1.1.6

  • 获取
使用场景

你可能感兴趣的:(java.utils.BitSet位存储)