Java Top.

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程

1.概述

在本教程中,我们将看到我们如何使用BitSetS.表示位的矢量。

首先,我们将从不使用Boolean []。然后熟悉了BitSet内部,我们将仔细看看它的API。

2.数组的

为了存储和操作位数组,有人可能会说我们应该使用Boolean []作为我们的数据结构。乍一看,这似乎可能是一个合理的建议。

然而,每个布尔基A.Boolean []通常消耗一个字节而不是一个位。因此,当我们有严格的内存要求时,或者我们只是针对减少的内存足迹,Boolean []远非理想。

让事情变得更加混凝土,让我们看看空间多少Boolean []有1024个元素消耗:

Boolean []位=新布尔值[1024];system.out.println(classlayout.parseinstance(bits).toprintable());

理想情况下,我们预期此阵列的1024位内存占用空间。然而,Java对象布局(JOL)揭示了完全不同的现实:

[Z对象内部:偏移大小类型说明值0 4(对象头)01 00 00000000000000000000 000000 0000 0000 00 00 00 0000 00(Object Header)00 00 0000 00 000000 00 0000 00 0000 00(0000000000000000 0000 00000000 00(0000000000000000 00)(0)8 4(对象报头)7b 12 07 00(01111011 00010010 00000111000000)(463483)12 4(对象报头)00 04 00 00(00000000 00000100 00000000000000)(1024)16 1024 Boolean [Z.n / a实例大小:1040字节

如果忽略对象头的开销,数组元素将消耗1024字节,而不是预期的1024位。这比我们预期的内存多了700%。

可寻址问题和词语撕裂主要原因是什么布尔基S不止一个比特。

要解决此问题,我们可以使用数字数据类型的组合(例如)和比特的操作。那是哪里的BitSet进来。

3.如何BitSet作品

如前所述,要实现每个标志内存使用一位,则使用BitSetAPI使用基本数字数据类型和位明智操作的组合。

为了简单起见,让我们假设我们将占八旗字节。首先,我们初始化这个单身的所有位字节零:

initial-bytes

现在,如果我们想在第三位设置一下真正的我们应该首先将数字1左转三个:

左移

然后它与当前的结果字节价值

最终或

如果决定在索引七个设置该位,则会发生同样的过程:

另一套

如上所示,我们按七位执行左移,并将结果与​​前一个相结合字节价值使用操作员。

3.1。获取位索引

检查特定位索引是否设置为真正的与否,我们将使用操作符。例如,下面是我们如何检查索引3是否设置:

  1. 在价值1上执行三个位的左移
  2. 安宁当前结果字节价值
  3. 如果结果大于零,则我们找到了一个匹配项,实际设置该位索引。否则,所请求的索引清晰或等于错误的
预备

上图显示了索引三的获取操作步骤。但是,如果我们询问明确的指数金宝搏官网188be,结果将不同:

清楚

结果等于零,索引四是清晰的。

3.2。日益增长的存储

目前,我们只能存储8位的向量。超越这个限制,我们只需要使用数组字节S,而不是一个字节,就是这样!

现在,每次我们需要设置,获取或清除特定索引,我们都应该先找到相应的数组元素。例如,让我们假设我们要设置索引14:

数组集

如上图所示,在找到正确的数组元素后,我们设置了适当的索引。

此外,如果我们想在这里设置超过15的索引,则BitSet将首先展开其内部数组。只有在展开数组并复制元素后,它才会设置请求的位。这有点类似于如何数组列表在内部工作。

到目前为止,我们使用了字节数据类型为简单起见。BitSet但是,API正在使用数组内部价值观

4.BitSetAPI

现在我们已经了解了这个理论,是时候看看了什么金宝搏官网188beBitSetAPI看起来像。

对于初学者来说,让我们比较a的内存占点BitSet实例的Boolean []我们早些时候见过:

Bitset Bitset =新Bitset(1024);system.out.println(graphlayout.parseinstance(bitset).toprintable());

这将打印浅大小的BitSet实例和其内部阵列的大小:

[电子邮件受保护]对象外部:地址大小类型路径值70f97d208 24 java.util.bitset(对象)70f97d220 144 [j。字[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

如上所示,它使用了长[]内部有16个元素(16 * 64位= 1024位)。反正,此实例总共使用168字节,而Boolean []使用1024字节

我们拥有的比特越多,足迹差异越多。例如,存储1024 * 1024位,Boolean []消耗1mb,并且BitSet实例消耗约130 kB。

4.1。构建BitSetS.

创建一个BitSet实例是使用no-arg构造函数

BitSet = new BitSet();

这会创建一个BitSet实例和一个长[]大小为1的。当然,如果需要,它可以自动生长此数组。

也可以创建一个BitSet初始位数

BitSet = new BitSet(100_000);

在这里,内部数组将有足够的元素来保存100,000位。当我们已经对要存储的比特数有了合理的估计时,这个构造函数就派上用场了。在这种情况下,它可以防止或减少不必要的复制数组元素,同时生长

甚至可以创造一个BitSet来自现有长[]字节[]长傻瓜,bytebuffer.。例如,我们在这里创建一个BitSet来自给定的实例长[]

BitSet BitSet = BitSet。价值Of(new long[] { 42, 12 });

有三个重载版本的价值()支持其他提到的类型的静态工厂方法。

4.2。设置位

我们可以将特定索引的值设置为真正的使用集(指数)方法:

BitSet = new BitSet();Bitset.set(10);assertthat(bitset.get(10))。Istrue();

与往常一样,这些指数以零为基础。甚至可以设置位的范围为真正的使用集(fromInclusive toExclusive)方法

Bitset.set(20,30);for(int i = 20; i <= 29; i ++){assertthat(bitset.get(i))。istrue();} assertthat(bitset.get(30))。Isfalse();

从方法签名中可以明显看出,开始索引是包含的,结束索引是排他的。

当我们说设置索引时,我们通常意味着将它设置为真正的。尽管存在此术语,我们可以将特定的位索引设置为错误的使用集(指数(布尔)方法:

bitSet。放(10, false); assertThat(bitSet.get(10)).isFalse();

此版本还支持设置一系列值:

bitset.set(20,30,false);for(int i = 20; i <= 29; i ++){assertthat(bitset.get(i))。Isfalse();}

4.3。清除位

而不是设置一个特定的位索引错误的,我们可以简单地清除它使用的清除(指数)方法:

bitSet.set (42);为了(bitSet.get (42) .isTrue ();Bitset.clear(42);assertthat(bitset.get(42))。isfalse();

而且,我们还可以清除一系列位明确(fromInclusive toExclusive)超载版本:

Bitset.set(10,20);for(int i = 10; i <20; i ++){assertthat(bitset.get(i))。istrue();Bitset.clear(10,20);for(int i = 10; i <20; i ++){assertthat(bitset.get(i))。Isfalse();}

有趣的是,如果我们在不传递任何参数的情况下调用这个方法,它将清除所有设置的位

Bitset.set(10,20);bitSet.clear ();for(int i = 0; i <100; i ++){assertthat(bitset.get(i))。Isfalse();}

如上所示,在召唤之后清除()方法,则将所有位设置为零。

4.4。获得位

到目前为止,我们使用了得到(索引)方法非常广泛。设置请求的位索引时,此方法将返回真正的。否则,它会回来错误的

bitSet.set (42);为了(bitSet.get (42) .isTrue ();为了(bitSet.get (43) .isFalse ();

如同清除,我们可以使用一系列比特索引get (fromInclusive toExclusive)方法:

Bitset.set(10,20);BitSet newBitSet = BitSet(10、20);For (int I = 0;我< 10;我+ +){为了(newBitSet.get(我).isTrue ();}

如上所示,此方法返回另一个方法BitSet在当前的[20,30)范围。也就是说,索引20Bitset.变量等价于索引0新手变量。

4.5。翻转位

要否定当前位索引值,我们可以使用翻转(指数)方法。也就是说,它会转身真正的价值观错误的反之亦然:

bitSet.set (42);bitset.flip(42);assertthat(bitset.get(42))。isfalse();bitset.flip(12);asserthat(bitset.get(12))。Istrue();

类似地,我们可以使用翻转(fromInclusive toExclusive)方法:

bitSet。翻转(30、40);For (int I = 30;我< 40;我+ +){为了(bitSet.get(我).isTrue ();}

4.6。长度

有三个类似长度的方法BitSet尺寸()方法返回内部阵列可以表示的位数。例如,由于无参数构造函数分配了一个长[]有一个元素的数组,那么尺寸()将为它返回64:

bitset defaultbitset = new bitset();asserthat(defaultbitset.size())。isequalto(64);

使用一个64位数,我们只能表示64位。当然,如果我们明确传递比特数:

Bitset Bitset =新Bitset(1024);asserthat(bitset.size())。isequalto(1024);

而且,这是Cardinality()方法表示A中的设置位数BitSet

asserthat(bitset.cardinality())。isequalto(0);bitset.set(10,30);assertthat(bitset.cardinality())。isequalto(30  -  10);

首先,该方法返回零,因为所有位都是零错误的。将[10,30)范围设置为真正的,那么Cardinality()方法调用返回20。

此外,长度()方法返回最后一个集位索引后的一个索引

assertthat(bitset.length())。isequalto(30);Bitset.set(100);asserthat(bitset.length())。isequalto(101);

起初,最后一个集合索引为29,因此此方法返回30.当我们将索引100设置为true时,那么长度()方法返回101。值得一提的是,如果所有位都是清除的,该方法将返回0

最后,isEmpty ()方法退货错误的数组中至少有一个集合位BitSet。否则,它会回来真正的

为了(bitSet.isEmpty ()) .isFalse ();bitSet.clear ();为了(bitSet.isEmpty ()) .isTrue ();

4.7。结合其他BitSetS.

相交(Bitset)方法采用另一个BitSet并返回真正的当两个BitSetS有一些共同点。也就是说,它们在同一索引中至少有一个集合位:

BitSet first = new BitSet();First.Set(5,10);BitSet second = new BitSet();第二。放(7, 15); assertThat(first.intersects(second)).isTrue();

[7,9]范围都设置在两者中BitSetS,这个方法返回真正的

也可以执行逻辑运行两者BitSetS.

﹐而且(第二次);为了(first.get (7) .isTrue ();为了(first.get (8) .isTrue ();为了(first.get (9) .isTrue ();为了(first.get (10)) .isFalse ();

这将执行逻辑两者之间BitSets并修改了第一的变量与结果。类似地,我们可以执行一个逻辑XOR.在两者BitSets,也是:

first.clear();First.Set(5,10);first.xor(第二);for(int i = 5; i <7; i ++){assertthat(first.get(i))。istrue();for(int i = 10; i <15; i ++){assertthat(first.get(i))。istrue();}

还有其他方法如而不是(BitSet)或者或(bitset)可以在两个中执行其他逻辑运算BitSet年代。

4.8。杂项

从Java 8开始,有一个流()方法将所有设置的位流化BitSet。例如:

BitSet = new BitSet();bitSet。放(15, 25); bitSet.stream().forEach(System.out::println);

这将把所有设置位打印到控制台。因为这将返回intstream.,我们可以执行常见的数字操作,例如求和,平均,计数等。例如,这里我们计算设置位的数量:

asserthat(bitset.stream()。count())。isequalto(10);

还,nextsetbit(fromIndex)方法将返回从fromIndex.

asserthat(bitset.nextsetbit(13))。isequalto(15);

fromIndex.本身包含在此计算中。什么时候没有真正的留在了BitSet,它会返回-1:

assertthat(bitset.nextsetbit(25))。isequalto(-1);

同样的,NextCletConbit(FromIndex)对象开始的下一个清除索引fromIndex.

assertthat(bitset.nextleClearbit(23))。Isequalto(25);

另一方面previousClearBit (fromIndex)返回相反方向上最近清除索引的索引:

assertthat(bitset.previoustlectlectbitbit(24))。Isequalto(14);

这同样适用于句前(fromIndex)

assertthat(bitset.previousetbit(29))。isequalto(24);assertthat(bitset.previousetbit(14))。isequalto(-1);

而且,我们可以转换aBitSet到A.字节[]或者长[]使用TobyteArray()TolongArray()方法,分别为:

byte [] bytes = bitset.tobytearray();longs [] longs = bitset.tolongarray();

5.结论

在本教程中,我们看到了我们如何使用BitSets表示位矢量。

一开始,我们熟悉了不使用背后的原理Boolean []表示位的矢量。然后我们看到了怎么样BitSet在内部工作,以及它的API是什么样子的。

像往常一样,所有的例子都是可用的在github上

Java底部

开始使用Spring 5和Spring Boot 2,通过学习春天课程:

>>查看课程
1评论
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!