有许多方法可以用来去重,比如使用列表、集合等等,但这些方法通常只适用于一般情况。然而,当涉及到大量数据去重时,常见的 Java Set、List,甚至是 Java 8 的新特性 Stream 流等方式就显得不太合适了。在处理大量数据的需求场景下,我们不得不提及 BitMap。
(1) 基本概念
位图(BitMap),基本思想就是用一个bit来标记元素,bit是计算机中最小的单位,也就是我们常说的计算机中的0和1,这种就是用一个位来表示的。
所谓位图,其实就是一个bit数组,即每一个位置都是一个bit,其中的取值可以是0或者1
像上面的这个位图,可以用来表示1,,4,6:
如果不用位图的话,我们想要记录1,4,,6 这三个整型的话,就需要用三个unsigned int,已知每个unsigned int占4个字节,那么就是34 = 12个字节,一个字节有8 bit,那么就是 128 = 96 个bit。
所以,位图最大的好处就是节省空间。
位图有很多种用途,特别适合用在去重、排序等场景中,著名的布隆过滤器就是基于位图实现的。
(2) 位图的优势
(3) 位图的劣势
但是位图也有着一定的限制,那就是他只能表示0和1,无法存储其他的数字。所以他只适合这种能表示true or false的场景。
以Java中的int为例,来对比观察BitMap的优势,再Java中,int类型通常需要32位,而BitMap使用1位就可以来标识此元素是否存在,所以可以认为BitMap占用的空间大小只有int类型的1/32,所以有大量数据判重时,使用BitMap也可以实现。
了解了什么是BitMap,那么我们就可以使用BitMap来解决大量数据去重的问题。
假设40亿个无符号整数数据都是10位的话,如果直接使用内存来存储,大约需要14.9GB 的空间。
考虑到其中有一些重复的数据,即使这样1G的空间基本上也是不够的。所以想要实现这个功能可以借助BitMap。
如果使用位图的话,40亿数据存储所需要的内存大概也就是 476M:
这样相比于之前的14.9G来说,大大的节省了很多空间。
比如要把数据"714771310"放到BitMap中,就需要找到第714771310这个位置,然后把他设置成1就可以了。
这样,把40亿个数字都放到BitMap之后,所有位置上是1的表示存在,不为1的表示不存在,相同的数据只需要设置一次1就可以了,那么,最终就把所有是1的数字遍历出来就行了。
BitMap在Java中的具体实现时java.util中的BitSet,BitSet是一个可变大小的位向量,能够动态增长以容纳更多的数据,以下是BitSet基本使用示例:
public class BitmapExample {
public static void main(String[] args) {
// 创建一个BitSet实例
BitSet bitMap = new BitSet();
// 设置第5个位置为1,表示第5个元素存在
bitMap.set(5);
// 检查第五个位置是否已经设置
boolean exists = bitMap.get(5);
// 输出:Element at position 5 exists: true
System.out.println("Element at position 5 exists: " + exists);
// 设置从索引10到20的所有位置为1,参数是包含起始点不包含终点的区间
bitMap.set(10, 21);
// 计算BitSet中所有位置为1的位的数量,相当于计算设置了的元素个数
int count = bitMap.cardinality();
System.out.println("Number of set bits: " + count);
// 清除第五个位置
bitMap.clear(5);
// 判断位图是否为空
boolean isEmpty = bitMap.isEmpty();
System.out.println("Is the BitSet empty after clearing some bits? " + isEmpty);
}
}