BitSet
BitSet
一、BitSet简介
类实现了一个按需增长的位向量。位 set 的每个组件都有一个boolean值。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个BitSet修改另一个BitSet的内容。
默认情况下,set 中所有位的初始值都是false。
每个位 set 都有一个当前大小,也就是该位 set 当前所用空间的位数。注意,这个大小与位 set 的实现有关,所以它可能随实现的不同而更改。位 set 的长度与位 set 的逻辑长度有关,并且是与实现无关而定义的。
除非另行说明,否则将 null 参数传递给BitSet中的任何方法都将导致NullPointerException。
在没有外部同步的情况下,多个线程操作一个BitSet是不安全的
二、基本原理
内部维护一个long数组words, 每一个bit位值为0或1即false和true, 初始只有一个long,所以BitSet最小的size是64,当随着存储的元素越来越多,BitSet内部会动态扩充(每次扩容为原来的两倍),最终内部是由N个long来存储;
用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用用的时候既可根据某一个是否为0表示,此数是否出现过。一个1G的空间,有8*1024*1024*1024=8.58*10^9bit,也就是可以表示85亿个不同的数。
三、Java中BitSet的实现
BitSet位于java.util这个包, 以下基于jdk8对BitSet实现进行分析;
3.1 部分属性
- ADDRESS_BITS_PER_WORD: 值为6, 一个long类型占8个byte, 有64bit, 而 2^6 = 64;
- BITS_PER_WORD: 值为1 << ADDRESS_BITS_PER_WORD = 64;
- BIT_INDEX_MASK: 索引掩码,
- words: BitSet内部维护的long类型数组, BitSet的核心;
- wordsInUse: 记录BitSet中words使用了的长度;
public class BitSet implements Cloneable, java.io.Serializable {
/*
* BitSets are packed into arrays of "words." Currently a word is
* a long, which consists of 64 bits, requiring 6 address bits.
* The choice of word size is determined purely by performance concerns.
*/
private final static int ADDRESS_BITS_PER_WORD = 6;
private final static int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD;
private final static int BIT_INDEX_MASK = BITS_PER_WORD - 1;
/* Used to shift left or right for a partial word mask */
private static final long WORD_MASK = 0xffffffffffffffffL;
/**
* The internal field corresponding to the serialField "bits".
*/
private long[] words;
/**
* The number of words in the logical size of this BitSet.
*/
private transient int wordsInUse = 0;
}
3.2 常用方法
BitSet主要分析的方法:
- 构造方法
- 反转某一位: flip
- 设置某一指定位: set
- 获取某一位: get
- 清空bit位: clear
- 获取size, length: size, length
- BitSet存储的数量: cardinality
- 下一个bit位: nextSetBit
1. 构造方法
- 无参构造, 默认words长度为1
public BitSet() {
initWords(BITS_PER_WORD); // 初始化words
sizeIsSticky = false;
}
- 有参构造, 根据nbits计算出words长度
public BitSet(int nbits) {
// nbits can't be negative; size 0 is OK
if (nbits < 0)
throw new NegativeArraySizeException("nbits < 0: " + nbits);
initWords(nbits);
sizeIsSticky = true;
}
- 初始化words;
// 根据nbits大小初始化 words的长度
private void initWords(int nbits) {
words = new long[wordIndex(nbits-1) + 1];
}
// 将bitIndex右移64位, 计算出bitIndex在words中的索引;
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
2. flip反转某一位
public void flip(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex);
words[wordIndex] ^= (1L << bitIndex);
recalculateWordsInUse();
checkInvariants();
}
3. 一个检查扩容;两个检查函数
- 检查扩容
// wordsInUse表示BitSet中使用的
private void expandTo(int wordIndex) {
int wordsRequired = wordIndex+1;
if (wordsInUse < wordsRequired) {
ensureCapacity(wordsRequired);
wordsInUse = wordsRequired;
}
}
private void ensureCapacity(int wordsRequired) {
if (words.length < wordsRequired) {
// Allocate larger of doubled size or required size
int request = Math.max(2 * words.length, wordsRequired);
words = Arrays.copyOf(words, request);
sizeIsSticky = false;
}
}
- 更新wordsInUse的值, wordsInUse的值可以被用来计算length;
private void recalculateWordsInUse() {
// Traverse the bitset until a used word is found
int i;
for (i = wordsInUse-1; i >= 0; i--)
if (words[i] != 0)
break;
wordsInUse = i+1; // The new logical size
}
- checkInvariants 可以看出是检查内部状态,尤其是wordsInUse是否合法的函数。
private void checkInvariants() {
assert(wordsInUse == 0 || words[wordsInUse - 1] != 0);
assert(wordsInUse >= 0 && wordsInUse <= words.length);
assert(wordsInUse == words.length || words[wordsInUse] == 0);
}
4. set
注意: 按位左移 若移动的位数超过被移动数类型的bit数, 则会对移位数取模, 再左移; 例如 11L<<65 等价于 11L << 1
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();
}
5. get
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);
}
6. 清空BitSet
- 清空所有的bit位,即全部置0。通过循环方式来以此以此置0。
public void clear() {
while (wordsInUse > 0)
words[--wordsInUse] = 0;
}
- 清空某一位
public void clear(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
int wordIndex = wordIndex(bitIndex);
if (wordIndex >= wordsInUse)
return;
words[wordIndex] &= ~(1L << bitIndex);
recalculateWordsInUse();
checkInvariants();
}
第一行是参数检查,如果bitIndex小于0,则抛参数非法异常。后面执行的是bitset中操作中经典的两步:a. 找到对应的long b. 操作对应的位。
a. 找到对应的long。 这行语句是 int wordIndex = wordIndex(bitIndex);
b. 操作对应的位。常见的位操作是通过与特定的mask进行逻辑运算来实现的。因此,首先获取 mask(掩码)。
对于 clear某一位来说,它需要的掩码是指定位为0,其余位为1,然后与对应的long进行&运算。
~(1L << bitIndex); 即获取mask
words[wordIndex] &= ; 执行相应的运算。
注意:这里的参数检查,对负数index跑出异常,对超出大小的index,不做任何操作,直接返回。
- 清空指定范围的那些bits
public void clear(int fromIndex, int toIndex) {
checkRange(fromIndex, toIndex);
if (fromIndex == toIndex)
return;
int startWordIndex = wordIndex(fromIndex);
if (startWordIndex >= wordsInUse)
return;
int endWordIndex = wordIndex(toIndex - 1);
if (endWordIndex >= wordsInUse) {
toIndex = length();
endWordIndex = wordsInUse - 1;
}
long firstWordMask = WORD_MASK << fromIndex;
long lastWordMask = WORD_MASK >>> -toIndex;
if (startWordIndex == endWordIndex) {
// Case 1: One word
words[startWordIndex] &= ~(firstWordMask & lastWordMask);
} else {
// Case 2: Multiple words
// Handle first word
words[startWordIndex] &= ~firstWordMask;
// Handle intermediate words, if any
for (int i = startWordIndex+1; i < endWordIndex; i++)
words[i] = 0;
// Handle last word
words[endWordIndex] &= ~lastWordMask;
}
recalculateWordsInUse();
checkInvariants();
}
方法是将这个范围分成三块,startword; interval words; stopword。
其中startword,只要将从start位到该word结束位全部置0;interval words则是这些long的所有bits全部置0;而stopword这是从起始位置到指定的结束位全部置0。
而特殊情形则是没有startword和stopword是同一个long。
具体的实现,参照代码,是分别作出两个mask,对startword和stopword进行操作。
7. size, length
- 获取size, size即为words数组中bit位的数量
public int size() {
return words.length * BITS_PER_WORD; // BITS_PER_WORD = 64
}
- length, 值等于使用了的最高的bit位索引 + 1;
public int length() {
if (wordsInUse == 0)
return 0;
return BITS_PER_WORD * (wordsInUse - 1) +
(BITS_PER_WORD - Long.numberOfLeadingZeros(words[wordsInUse - 1]));
}
8. cardinality, BitSet中存储的数据个数
- Long.bitCount(words[i]): 计算出一个long类型的二进制中1的个数;
public int cardinality() {
int sum = 0;
for (int i = 0; i < wordsInUse; i++)
sum += Long.bitCount(words[i]);
return sum;
}
9. nextSetBit
/**
* fromIndex后面为bit为值为1的bit索引
* @param fromIndex [description]
* @return [description]
*/
public int nextSetBit(int fromIndex) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex < 0: " + fromIndex);
checkInvariants();
int u = wordIndex(fromIndex);
if (u >= wordsInUse)
return -1;
long word = words[u] & (WORD_MASK << fromIndex);
while (true) {
if (word != 0)
return (u * BITS_PER_WORD) + Long.numberOfTrailingZeros(word);
if (++u == wordsInUse)
return -1;
word = words[u];
}
四、Java中Bitset的使用
由于BitSet中可以实现1G的内存存储8亿个不同的数据, 因此可以用来处理大量数据的情况;
使用场景:
- 对海量数据进行一些统计工作的时候,比如日志分析、用户数统计
如统计40亿个数据中没有出现的数据,将40亿个不同数据进行排序等。
现在有1千万个随机数,随机数的范围在1到1亿之间。现在要求写出一种算法,将1到1亿之间没有在随机数中的数求出来
- mq中, 防止消息被重复消费;
package com.ricky.java.test;
import java.util.BitSet;
public class BitSetTest {
/**
* @param args
*/
public static void main(String[] args) {
// 字符串去除重复出现的字符
containChars("abcdfab");
// 对数组数据进行排序, 去重;
sortArray(new int[] { 423, 700, 9999, 2323, 356, 6400, 1,2,3,2,2,2,2 });
}
/**
* 字符串去除重复出现的字符
* @param str [description]
*/
public static void containChars(String str) {
BitSet used = new BitSet();
for (int i = 0; i < str.length(); i++)
used.set(str.charAt(i)); // set bit for char
StringBuilder sb = new StringBuilder();
sb.append("[");
int size = used.size();
System.out.println(size);
for (int i = 0; i < size; i++) {
if (used.get(i)) {
sb.append((char) i);
}
}
sb.append("]");
System.out.println(sb.toString());
}
/**
* 对数组去重排序
*/
public static void sortArray(int[] array) {
BitSet bitSet = new BitSet(2 << 13);
// 虽然可以自动扩容,但尽量在构造时指定估算大小,默认为64
System.out.println("BitSet size: " + bitSet.size());
for (int i = 0; i < array.length; i++) {
bitSet.set(array[i]);
}
//剔除重复数字后的元素个数
int bitLen=bitSet.cardinality();
//进行排序,即把bit为true的元素复制到另一个数组
int[] orderedArray = new int[bitLen];
int k = 0;
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
orderedArray[k++] = i;
}
System.out.println("After ordering: ");
for (int i = 0; i < bitLen; i++) {
System.out.print(orderedArray[i] + "\t");
}
System.out.println("iterate over the true bits in a BitSet");
//或直接迭代BitSet中bit为true的元素iterate over the true bits in a BitSet
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
System.out.print(i+"\t");
}
}
}