前言:
在Java中,整型(int)的最大值MAX_VALUE = 2^31 - 1。而整型数组(int [ ])的下标是从0开始的,也就意味着一个整型数组最多存放2^31 - 1个元素。这种存储方式优点很明显:无论元素大小(不超过整型范围),每个元素所占的空间都是32位的,可以存下很大的数字;缺点是空间浪费的现象比较明显。
什么是位图?其原理是什么?
位图(Bitset)是一种在整型数组的基础上充分利用空间的一种数据结构。其实就是利用bit位的取值来“存放值”,bit位是1代表元素存在、bit位是0代表元素不存在。每个bit位都可以代表一个元素,极大的节省了空间。
由上图我们可以看到,如果我们初始化一个整型数组的长度为8,那么这个数组最多能存储的元素个数就是8;如果我们使用位图,每个索引下标都由32个bit位组成,每个bit位都能单独的表示一个元素是否存在,那么8个索引下标所能表示的元素个数是:32*8 = 256。
位图有什么缺点?
位图的缺陷很明显,它只能表示连续的元素,需要在初始化位图时就声明它所能表示的元素范围。
如上图所示,虽然位图能存储的元素数量变得更多了,但它无法像整型数组那样随心所欲的存储任意元素(值不超过整型范围)。
如何实现位图?
(1)构造位图:位图是基于整型数组实现的,所以成员变量需要一个整型数组;在创建位图时,需要声明它所能表示的元素范围,需要传递一个整型形参n,代表位图能表示的元素范围是:[0,n-1]。
(2)添加元素:将表示元素的bit位设置成1。
(3)移除元素:将表示元素的bit位设置成0。
(4)“反转”元素:元素存在于位图中,则移除;元素不存在于位图中,则创建。
(5)判断元素:判断元素是否存在于位图中。
如何验证位图的准确性?
使用HashSet来验证位图是否真的能正确地添加、删除、“反转”元素。
Code:
int n = 1000; int testTimes = 10000; //位图能表示的元素范围:[0,999] BitSet bitSet = new BitSet(n); HashSet<Integer> hashSet = new HashSet<>(); System.out.println("测试阶段开始"); for (int i = 0; i < testTimes; i++) { //Math.random():生成一个double类型的值,范围在[0,1)之间 double random = Math.random();//随机概率值 //x的值在[0,1000)之间,不会超过位图所能表示的范围 int x = (int) (Math.random() * n);//待添加的元素值 if(random < 0.333){ bitSet.add(x); hashSet.add(x); }else if(random < 0.666){ bitSet.remove(x); hashSet.remove(x); }else{ bitSet.reverse(x); if (hashSet.contains(x)){ hashSet.remove(x); }else { hashSet.add(x); } } } System.out.println("验证阶段开始"); for (int i = 0; i < n; i++) { if(bitSet.contains(i) != hashSet.contains(i)) System.out.println("出错了"); } System.out.println("验证阶段结束");
执行结果: