一、BitSet基础
Bitset,也就是位图,由于可以用非常紧凑的格式来表示给定范围的连续数据而经常出现在各种算法设计中。基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用的时候既可根据某一个是否为0表示此数是否出现过。
例如:一个1G的空间,有8*1024*1024*1024 = 8.58*10^9bit,也就是可以表示85亿个不同的数。
常见的应用是那些需要对海量数据进行一些统计工作的时候,比如日志分析等。面试题中也常出现,比如:统计40亿个数据中没有出现的数据,将40亿个不同数据进行排序等。
二、java中BitSet类
大小可动态改变,取值为true或false的位集合。用于表示一组布尔标志。
此类实现了一个按需增长的位向量。位set的每个组件都有一个boolean值。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个BitSet修改另一个BitSet的内容。默认情况下,set 中所有位的初始值都是 false。
每个位set都有一个当前大小,也就是该位set当前所用空间的位数。注意,这个大小与位set的实现有关,所以它可能随实现的不同而更改。位set的长度与位set的逻辑长度有关,并且是与实现无关而定义的。
除非另行说明,否则将null参数传递给BitSet中的任何方法都将导致NullPointerException。在没有外部同步的情况下,多个线程操作一个BitSet是不安全的。
实例:
package com.zxt.bitset;
import java.util.BitSet;
public class BitSetSizeTest {
// BitSet默认的构造函数声明一个64位的BitSet,值都是false。如果你要用的位超过了默认size,它会再申请64位,而不是报错。
// 你申请的位都是以64为倍数的,就是说你申请不超过一个64的就按64算,超过一个不超过2个的就按128算。......
public static void main(String[] args) {
BitSet bm = new BitSet();
System.out.println(bm.isEmpty() + "--" + bm.size());
bm.set(0);
System.out.println(bm.isEmpty() + "--" + bm.size());
System.out.println(bm.get(65));
System.out.println(bm.isEmpty() + "--" + bm.size());
bm.set(65);
System.out.println(bm.isEmpty() + "--" + bm.size());
BitSet bm1 = new BitSet(7);
System.out.println(bm1.isEmpty() + "--" + bm1.size());
BitSet bm2 = new BitSet(63);
System.out.println(bm2.isEmpty() + "--" + bm2.size());
BitSet bm3 = new BitSet(65);
System.out.println(bm3.isEmpty() + "--" + bm3.size());
BitSet bm4 = new BitSet(111);
System.out.println(bm4.isEmpty() + "--" + bm4.size());
}
}
三、BitSet类方法
方法 | 说明 |
BitSet() or BitSet(int nbits) | 构造函数 |
public void set(int pos) | 位置pos的字位设置为true |
public void set(int bitIndex, boolean value) | 将指定索引处的位设置为指定的值 |
public void clear(int pos) | 位置pos的字位设置为false |
public void clear() | 将此BitSet中的所有位设置为false |
public int cardinality() | 返回此BitSet中设置为true的位数 |
public boolean get(int pos) | 返回位置是pos的字位值 |
public void and(BitSet other) | other同该字位集进行与操作 结果作为该字位集的新值 |
public void or(BitSet other) | other同该字位集进行或操作 结果作为该字位集的新值 |
public void xor(BitSet other) | other同该字位集进行异或操作 结果作为该字位集的新值 |
public void andNot(BitSet set) | 清除此BitSet中所有的位 set用来屏蔽此BitSet的BitSet |
public int size() | 返回此 BitSet 表示位值时实际使用空间的位数 |
public int length() | 返回此 BitSet 的“逻辑大小” BitSet 中最高设置位的索引加 1 |
public int hashCode() | 返回该集合Hash码,这个码同集合中的字位值有关 |
public boolean equals(Object other) | other中的字位同集合中的字位相同,返回true |
public Object clone() | 克隆此BitSet,生成一个与之相等的新 BitSet |
public String toString() | 返回此位set的字符串表示形式 |
实例:标明一个字符串中出现了那些字符
package com.zxt.bitset;
import java.util.BitSet;
public class WhichChars {
private BitSet used = new BitSet();
// 判断一个字符串中的那些字符出现过
public static void main(String args[]) {
WhichChars w = new WhichChars("How do you do");
System.out.println(w);
}
public WhichChars(String str) {
for (int i = 0; i < str.length(); i++) {
// set bit for char
used.set(str.charAt(i));
}
}
public String toString() {
String desc = "[";
int size = used.size();
for (int i = 0; i < size; i++) {
if (used.get(i)) {
desc += (char) i;
}
}
return desc + " ]";
}
}
四、BitSet的使用
java.util.BitSet可以按位存储。
计算机中一个字节(byte)占8位(bit),我们java中数据至少按字节存储的,比如一个int占4个字节。如果遇到大的数据量,这样必然会需要很大存储空间和内存。
如何减少数据占用存储空间和内存可以用算法解决。java.util.BitSet就提供了这样的算法。比如有一堆数字,需要存储,source=[3,5,6,9],用int就需要4*4个字节。
java.util.BitSet可以存true/false。如果用java.util.BitSet,则会少很多,其原理是:
1,先找出数据中最大值maxvalue = 9
2,声明一个BitSet bs,它的size是maxvalue+1 = 10
3,遍历数据source,bs[source[i]]设置成true.
最后的值是:(0为false;1为true)
bs [0, 0, 0, 1, 0,1, 1, 0, 0, 1]
3, 5, 6, 9
这样一个本来要int型需要占4字节共32位的数字现在只用了1位!比例32:1。这样就省下了很大空间。
实例:
package com.zxt.bitset;
import java.util.BitSet;
public class BitSetUse {
public static void main(String[] args) {
int[] shu = { 2, 42, 5, 6, 6, 18, 33, 15, 25, 31, 28, 37 };
int maxvalue = BitSetUse.getMaxValue(shu);
BitSet bm = new BitSet(maxvalue);
System.out.println("maxvalue--" + maxvalue);
System.out.println("bm.size()--" + bm.size());
// 放值
for (int i = 0; i < shu.length; i++) {
bm.set(shu[i], true);
}
printBitSet(bm);
}
// 打印
public static void printBitSet(BitSet bs) {
StringBuffer buf = new StringBuffer();
buf.append("[\n");
for (int i = 0; i < bs.size(); i++) {
if (i < bs.size() - 1) {
buf.append(BitSetUse.getBitTo10(bs.get(i)) + ",");
} else {
buf.append(BitSetUse.getBitTo10(bs.get(i)));
}
if ((i + 1) % 8 == 0 && i != 0) {
buf.append("\n");
}
}
buf.append("]");
System.out.println(buf.toString());
}
// 找出数据集合最大值
public static int getMaxValue(int[] zu) {
int max = zu[0];
for (int i = 0; i < zu.length; i++) {
if (max < zu[i]) {
max = zu[i];
}
}
return max;
}
// true, false换成1, 0为了好看
public static String getBitTo10(boolean flag) {
return flag ? "1":"0";
}
}
这样便完成了存值和取值。注意它会对重复的数字过滤,就是说,一个数字出现过超过2次的它都记成1。出现的次数这个信息就丢了。Bitmap的常见扩展,是用2位或者更多为来表示此数字的更多信息,比如出现了多少次等。