【JAVA基础】BitSet

来源:互联网 发布:程序员之死女主角 编辑:程序博客网 时间:2024/06/05 02:58

  • 修改记录
  • 名词解释
  • 存储实现
    • 1 setint bitIndex
    • expandTo
    • ensureCapacity

1 修改记录

时间 描述 2017-12-07 建档

2 名词解释

为了方便讲解和理解,本文自定义了部分名词,再次说明:

  • 【目标数字】 bitIndex,指需要被存储的数字。通常是BitSet.set()方法的参数;

  • 【仓库数字】 word,指用来存储目标数字的long类型数字。默认该数字为0L;

  • 【仓库】 words,指仓库数字的集合。默认长度为1;

  • 【需求数量】 wordsRequired,指存储目标数字时需要用到的仓库数字数量。仓库数字是64位(位索引范围是0-63)long类型,则仓库第一个(索引为0)的仓库数字可以独立存储0-63的目标数字;当存储64-127时,则需要2个仓库数字;

  • 【有效数量】 wordsInUse,指用来存储最大目标数字时所用的仓库数字的数量,也能表示BitSet能存储的数字的有效范围。例如,BitSet中存储了{1,60,120}三个数字,则有效数量就是用来存储120所用到的仓库数量,即2;2个有效仓库数字能存储0-127的数字,那么BitSet当前的有效范围就是[0, 128);

BitSet用64位二进制中每一位的索引来存储数字。例如,存储数字3,就将二进制中从右往左数的第3位设置成1。该二进制初始化时为64个0。

该二进制数转化成long类型的仓库数字存储在一个long数组中words,即仓库。当存储的数字大于63(即超出了一个二进制long数据的长度)时,仓库就会新增一个仓库数字合作存储它。

对比传统的存储方式,一个数字(例如3)以int方式存储,需要4个字节32位;而利用BitSet,只需要1位即可表示该数字。

3 存储实现

3.1 set(int bitIndex)

步骤1:判断目标数字是否小于0:true-抛出异常,false-进入步骤2;

if (bitIndex < 0)    throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);

步骤2:计算存储了目标数字的位数据所在仓库数字的索引。例如,存储了60的位数据在索引为60/64=0的仓库数字中。

int wordIndex = wordIndex(bitIndex);

具体计算方法如下。因为仓库数字是long类型,长度为64位(2的6次方),目标数字作为索引不能超过这个长度,因此计算方式是目标数字除64取整,也即目标数字右移6位:

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

步骤3:调整BitSet的有效范围。

expandTo(wordIndex);

步骤4:设置存储位。需要注意的是JAVA的位移操作,如果位移的位数大于数据类型的长度,JAVA并不会位移那么多位,而是只会位移位数%类型长度的位数。例如,对1进行左位移120位操作时,实际只位移120%32=24位,其余补0;对1L进行左位移120位操作时,JAVA只位移120%64=56位,其余补0。

words[wordIndex] |= (1L << bitIndex);

例如,存储120时,第一个仓库数字不变,第二个仓库数字与2的56次方进行或操作,即将第二个仓库数字的第57位(下标为63+57=120)设置成1。

注意:之所以是63+57,是因为第一个仓库数字的索引是从0开始的,第64位的索引是63;而除第一个仓库数字之外的其他仓库数字的索引均是从1开始的,或者将他们单纯地理解为增量。

步骤5:检验。

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

expandTo()

确保BitSet的有效范围。如果有效数量小于的需求数量,则需提升BitSet的有效范围。

private void expandTo(int wordIndex) {    // 需求数量:仓库数字索引+1    // 例如,目标数字为2,则仓库数字索引是2/64=0,需求数量是0+1=1,即只需要一个仓库数字就能存储目标数字2;    // 同理,目标数字为67,则仓库数字索引是67/64=1,需求数量是1+1=2,即需要两个仓库数字才能存储目标数字67。    int wordsRequired = wordIndex+1;    // 如果有效数量小于需求数量,表示目前有效仓库数字的数量不足以存储该数字,需提升有效仓库数字的数量    if (wordsInUse < wordsRequired) {        ensureCapacity(wordsRequired);        wordsInUse = wordsRequired;    }}

ensureCapacity()

仓库words自动扩容。执行到这儿,说明目标数字是当前BitSet存储的最大数字,有效数量无法满足存储该数字的需求,因此需要提升有效仓库数字的数量。

private void ensureCapacity(int wordsRequired) {}

如果仓库现有的长度不够,默认2倍递增;如果2倍依然不够,则以需求数量为扩容标准进行扩容。

例如,仓库当前长度是4,每个仓库数字是64位,如果目标数字是258,则wordRequired=258/64(=4)+1=5,仓库长度不够,默认按两倍扩容至8,能满足需求数量(5);如果目标数字是513,则wordRequired=512/64(=8)+1=9,仓库长度不够,默认2倍扩容至8,无法满足需求数量9,因此BitSet会扩容至9。

if (words.length < wordsRequired) {    int request = Math.max(2 * words.length, wordsRequired);    words = Arrays.copyOf(words, request);}
原创粉丝点击