面试题:如何在4亿个正整数中快速判断某个整数是否存在,假设所有的整数都是4字节整型,可用的内存1G。
这个问题有几种思路:
- 顺序查找目标整数。因为可用内存只有1G,没法将4亿个整数全部加载到内存中查找,只能先将数据保存到文件中,然后顺序读取文件直到找到目标整数。时间复杂度为O(n).而且磁盘的IO时间为毫秒级,所以顺序读取完全部数据所需时间相当可观,实际测试需要1分钟左右。性能上不能满足题目要求。
- Hashmap实现。时间复杂度O(1),如果没有内存限制的前提下这种方法能够满足要求。可是现在内存限制1G,Hashmap没有足够的空间装下所有数据,所以此方法不可行。
- 位图实现。题设给出的是一堆4字节整数,除了用Integer来表示某个整数以外,其实我们还能用二进制位来表示。比如我们要用二进制位表示1,2,4,8 如下,分别将二进制的第1,2,4,8位置为1:
总共才只需1个字节大小,比起用Integer表示节省了15个字节。 1G内存足够存储题设的所有整数。而且位图的查找高效,时间复杂度为O(1).
使用JAVA提供的BitSet类可以容易地实现位图计算。下面的例子将1到4亿的整数保存在文件中,然后从文件读取到BitSet然后判断某个数是否存在。
- 将1到4亿的数字保存到文件中:
@Test
public void writeManyIntegers(){
try(PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(Paths.get("D:\\tmp\\many_integers.txt")))){
for(long i = 1; i<= 400000000; i++){
printWriter.println(i);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- 加载到BitSet中并测试:
@Test
public void testBitSet() throws IOException {
BitSet bitSet = new BitSet();
Files.lines(Paths.get("D:\\tmp\\many_integers.txt")).forEach(line-> bitSet.set(Integer.parseInt(line)));
System.out.printf("存在 0 ?%s\n",bitSet.get(0));
System.out.printf("存在 1 ?%s\n",bitSet.get(1));
System.out.printf("存在 400000000 ?%s\n",bitSet.get(400000000));
System.out.printf("存在 400000001 ?%s\n",bitSet.get(400000001));
System.out.printf("数据占用的总大小:%dm\n",bitSet.size()/8/1024/1024);
}
- 运行结果:
BitSet只支持4字节整数的操作,如果数据量更大,可以考虑将数据合理拆分到多个BitSet中。
需要注意的是:
- 位图通常是用于大数据量较分散的数字表示,当只有少量数据的时候可能不是最佳的选择。比如只将一个较大的整数如 10000 保存在位图中,则需要创建一个至少为10000 bits的位图,比起用常规的Integer表示反而浪费了更多的内存空间。
- BitSet不是线程安全的,并发场景下需要做好控制。