java bitset用途_Java的BitSet原理及应用

4fbad3a6d253?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

Java的BitSet原理及应用

原理

众所周知,Java的BitSet使用一个Long(一共64位)的数组中的没一位(bit)是否为1来表示当前Index的数存在不。但是BitSet又是如何实现的呢?其实只需要理解其中的两个方法:

set

get

就能够理解BitSet的实现原理是什么了。

set

先看源代码:

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

checkInvariants();

}

除了判断给的值是否小于0的那两句,我们依次来看一下这个函数的每一句代码。

wordIndex

第一句就是计算wordIndex,通过wordIndex函数获取值。代码如下:

private static int wordIndex(int bitIndex) {

return bitIndex >> ADDRESS_BITS_PER_WORD;

}

这里ADDRESS_BITS_PER_WORD的值是6,那么最先想到的问题就是:为什么是6呢?而不是其他值呢?

答案其实很简单,还记得在最开始提到的:BitSet里使用一个Long数组里的每一位来存放当前Index是否有数存在。

因为在Java里Long类型是64位,所以一个Long可以存储64个数,而要计算给定的参数bitIndex应该放在数组(在BitSet里存在word的实例变量里)的哪个long里,只需要计算:bitIndex / 64即可,这里正是使用>>来代替除法(因为位运算要比除法效率高)。而64正好是2的6次幂,所以ADDRESS_BITS_PER_WORD的值是6。

通过wordIndex函数就能计算出参数bitIndex应该存放在words数组里的哪一个long里。

expandTo

private void expandTo(int wordIndex) {

int wordsRequired = wordIndex+1;

if (wordsInUse < wordsRequired) {

ensureCapacity(wordsRequired);

wordsInUse = wordsRequired;

}

}

从上面已经知道在BitSet里是通过一个Long数组(words)来存放数据的,这里的expandTo方法就是用来判断words数组的长度是否大于当前所计算出来的wordIndex(简单的说,就是能不能存的下),如果超过当前words数组的长度(记录在实例变量wordsInUse里),也即是存不下,则新加一个long数到words里(ensureCapacity(wordsRequired)所实现的。)。

Restores invariants

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

这一行代码可以说是BitSet的精髓了,先不说什么意思,我们先看看下面代码的输出:

System.out.println(1<<0);

System.out.println(1<<1);

System.out.println(1<<2);

System.out.println(1<<3);

System.out.println(1<<4);

System.out.println(1<<5);

System.out.println(1<<6);

System.out.println(1<<7);

输出是:

1

2

4

8

16

32

64

128

这个输出看出规律没有?就是2的次幂,但是还是不太好理解,我们用下面的输出,效果会更好:

System.out.println(Integer.toBinaryString(1<<0));

System.out.println(Integer.toBinaryString(1<<1));

System.out.println(Integer.toBinaryString(1<<2));

System.out.println(Integer.toBinaryString(1<<3));

System.out.println(Integer.toBinaryString(1<<4));

System.out.println(Integer.toBinaryString(1<<5));

System.out.println(Integer.toBinaryString(1<<6));

System.out.println(Integer.toBinaryString(1<<7));

输出是:

1

10

100

1000

10000

100000

1000000

10000000

从而发现,上面所有的输出力,1 所在的位置,正好是第1,2,3,4,5,6,7,8(Java数组的Index从0开始)位。而BitSet正是通过这种方式,将所给的bitIndex所对应的位设置成1,表示这个数已经存在了。这也解释了(1L << bitIndex)的意思(注意:因为BitSet是使用的Long,所以要使用1L来进行位移)。

搞懂了(1L << bitIndex),剩下的就是用|来将当前算出来的和以前的值进行合并了words[wordIndex] |= (1L << bitIndex);。

剩下的checkInvariants就没什么好解释的了。

get

搞懂了set方法,那么get方法也就好懂了,整体意思就是算出来所给定的bitIndex所对应的位数是否为1即可。先看看代码:

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

}

计算wordIndex在上面set方法里已经说明了,就不再细述。这个方法里,最重要的就只有:words[wordIndex] & (1L << bitIndex)。这里(1L << bitIndex)也已经做过说明,就是算出一个数,只有bitIndex位上为1,其他都为0,然后再和words[wordIndex]做&计算,如果words[wordIndex]数的bitIndex位是0,则结果就是0,以此来判断参数bitIndex存在不。

运用

搞清楚了原理,就可以很好的运用了,不过这里不讲怎么使用Java的BitSet,网上有很多。我讲一个我在刷Leetcode时碰见的一个题,就可以使用BitSet的原理来实现。

题目如下:

Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array.

For example, Given nums = [0, 1, 3] return 2.

Note: Your algorithm should run in linear runtime complexity. Could you implement it using only constant extra space complexity?

翻译过来就是:从0到n之间取出n个不同的数,找出漏掉的那个。 注意:你的算法应当具有线性的时间复杂度。你能实现只占用常数额外空间复杂度的算法吗?

这正好可以使用BitSet的思想来实现。

public int missingNumberInByBitSet(int[] array) {

int bitSet = 0;

for (int element : array) {

bitSet |= 1 << element;

}

for (int i = 0; i < array.length; i++) {

if ((bitSet & 1 << i) == 0) {

return i;

}

}

return 0;

}

这里是简单实现,使用int,而不是lang,也没有用一个数组,但是核心思想是这样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值