位图算法:bitmap是内存中连续的二进制位(bit)所组成的数据结构,位图算法主要用于对大量整数做去重和查询操作(交集、并集、差集)。因为使用的是位运算,因此性能也是很高的。
场景一:给用户打上标签,然后对各标签进行用户数据的统计。如果标签数不多的话,以用户为中心建立标签信息就足够了。如果便签种类很大,此时应当以标签为中心,用标签来关联用户,为每个标签建立相应的位图,通过位图来统计用户信息。
场景二:给40亿个不重复的无符号整数,没拍过序。给定一个无符号整数,如何可以高效的判断是否存在这些数据中。先通过set方法将整数放入位图中,再通过get方法判断整数是否存在。
jdk中的BitSet就是位图算法的具体实现。
对于左移操作:如果对于基本类型数据a进行移位操作,设a占用的bit位为count,移位shift位。则 a >> shift 等价于 a >> (shift % count)。
运算时数据溢出:计算机运算时采用补码方式,当数据溢出时,数据从右侧开始保留指定类型位数,再转为原码得到相应的值。
如:byte a = (byte)(127 + 2) 结果为
127的补码:1111 1111
2的补码:0000 0010
补码结果为:1 0000 0001 溢出后截取为 1000 0001(保留符号位)
转原码:1111 1111 (-127)
public class BitSet implements Cloneable, java.io.Serializable {
//一个long元素有64bit,需要移动6位
private final static int ADDRESS_BITS_PER_WORD = 6;
//位图中的bit信息存入long型数组中,我们不关心long值为多少,而关心每一bit位是1或是0.
//若只有两个元素,则组成的位图结构为words[1] words[0]对应的二进制
private long[] words;
//记录当前用到的数组索引
private transient int wordsInUse = 0;
private static int wordIndex(int bitIndex) {
return bitIndex >> ADDRESS_BITS_PER_WORD;
}
//往指定的bit位设置值,设置为1
//1L << 65 等价于 1L << 1 即 1L << (65 % 64),编译时自动处理
// 1 << 33 等价于 1 << 1 即 1 << (33 % 32)
public void set(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
//根据指定的bit位,找到它对应的数组位置
int wordIndex = wordIndex(bitIndex);
expandTo(wordIndex);
//将bitIndex处的值置为1
words[wordIndex] |= (1L << bitIndex); // Restores invariants
checkInvariants();
}
//查询指定的bit位的值,如果为1则返回true,否则返回false
public boolean get(int bitIndex) {
if (bitIndex < 0)
throw new IndexOutOfBoundsException("bitIndex < 0: " + bitIndex);
checkInvariants();
int wordIndex = wordIndex(bitIndex);
//如果指定的bit位的值为1返回true,否则返回false
return (wordIndex < wordsInUse)
&& ((words[wordIndex] & (1L << bitIndex)) != 0);
}
//两个位图结构的与元素,即交集
public void and(BitSet set) {
if (this == set)
return;
while (wordsInUse > set.wordsInUse)
words[--wordsInUse] = 0;
// Perform logical AND on words in common
for (int i = 0; i < wordsInUse; i++)
words[i] &= set.words[i];
recalculateWordsInUse();
checkInvariants();
}
//两个位图结构的或运算,即并集
public void or(BitSet set) {
if (this == set)
return;
int wordsInCommon = Math.min(wordsInUse, set.wordsInUse);
if (wordsInUse < set.wordsInUse) {
ensureCapacity(set.wordsInUse);
wordsInUse = set.wordsInUse;
}
// Perform logical OR on words in common
for (int i = 0; i < wordsInCommon; i++)
words[i] |= set.words[i];
// Copy any remaining words
if (wordsInCommon < set.wordsInUse)
System.arraycopy(set.words, wordsInCommon,
words, wordsInCommon,
wordsInUse - wordsInCommon);
// recalculateWordsInUse() is unnecessary
checkInvariants();
}
//两个位图结构的异或运算,等同于取非,需要有全量位图
public void xor(BitSet set) {
int wordsInCommon = Math.min(wordsInUse, set.wordsInUse);
if (wordsInUse < set.wordsInUse) {
ensureCapacity(set.wordsInUse);
wordsInUse = set.wordsInUse;
}
// Perform logical XOR on words in common
for (int i = 0; i < wordsInCommon; i++)
words[i] ^= set.words[i];
// Copy any remaining words
if (wordsInCommon < set.wordsInUse)
System.arraycopy(set.words, wordsInCommon,
words, wordsInCommon,
set.wordsInUse - wordsInCommon);
recalculateWordsInUse();
checkInvariants();
}
}