9.6.5 BitSet
BitSet存储一个bit序列(BitSet并不符合数学上的Set定义,BitVector或BitArray这样的名字会更合适)。在需要存储bit序列(例如flags)时,BitSet底层实现比ArrayList<Boolean>更紧凑,因此也更高效。
BitSet可以方便地读取、设置和重置单个bit。而用int或long存储bit序列时需要进行繁琐的masking和位操作。
C++ Note
C++ bitset模板提供与Java BitSet类相同的功能
java.util.BitSet (from Version1.0)
方法 | 描述 |
---|---|
BitSet(int initialCapacity) | 构造方法 |
int length() | 返回BitSet的长度 |
boolean get(int bit) | 读取一个bit |
void set(int bit)) | Set一个bit |
void clear(int bit) | Reset一个bit |
void and(BitSet set) | 与set进行逻辑AND |
void or(BitSet set) | 与set进行逻辑OR |
void xor(BitSet set) | 与set进行逻辑XOR |
void andNot(BitSet set) | 与(set的逻辑NOT)进行逻辑AND |
我们通过实现“Eratosthenes筛”算法来展示BitSet的用法。(这其实并不是寻找素数的最好方法。但出于某种原因,该算法被普遍用作测试编译器性能的一个benchmark。然而它也不是一个好的benchmark,因为它主要测试位操作。)
我们的程序统计2到2000000之间的素数个数。其思路很简单:遍历一个包含2000001 bit的BitSet,BitSet中第n位bit值就代表数值n的flag(例如,如果第1000位为false,就表示数值1000不是素数)。首先,将所有位置true;然后,如果一个数已知是素数,则该数的倍数就不是素数,将与它们相对应的位都改为false。整个迭代过程完成后,那些仍然为true的位就都是素数了。Listing 9.8是Java程序,Listing 9.9是对应的C++程序。
虽然筛算法算不上一个好的benchmark,我们还是忍不住用它来测试了一下这两种实现。看看在一台具有2.9GHz酷睿双核处理器,8G内存,运行Ubuntu 14.04的ThinkPad上的结果:
C++(g++ 4.6.3): 390ms
Java(Java SE8): 119ms
我们已经在本书的九个版本中运行了测试。在后面五个版本中,Java都轻松击败了C++。但是如果我们调高C++编译器的优化水平,它会比Java快上33ms。只有在程序运行了足够多次,以致引发Hotspot JIT编译后,Java才能也这么快。
//Listing 9.8 Sieve/Sieve.java
import java.util.*;
public class Sieve{
public static void main(String[] args){
int n = 2000000;
BitSet b = new BitSet(n+1);
int count = 0;
int i;
for(i = 2; i<=n; i++)
b.set(i);
i = 2;
while(i*i <= n){
if(b.get(i)){
count++;
int k = 2*i;
while(k<=n){
b.clear(k);
k+=i;
}
}
i++;
}
while(i<=n){
if(b.get(i)) count++;
}
}
}
//Listing 9.9 sieve/sieve.cpp
#include<bitset>
#include<iostream>
using namespace std;
int main()
{
const int N = 2000000;
bitset<N+1> b;
int count = 0;
int i;
for(i =2; i<=N; i++)
b.set(i);
i = 2;
while(i*i<=N)
{
if(b.test(i))
{
count++;
int k = 2*i;
while(k<=N)
{
b.reset(k);
k+=i;
}
}
i++;
}
while(i<=N)
{
if(b.test(i))
count++;
i++;
}
return 0;
}