运行结果: 63 64 67 88 90 92 200 230 283 958
有这样一道题目,有一个文件,里边有4000000000个正整数,每个数一行,有可能有重复的数,目的查询一个目标值在不在文件中。
要求:查询时间复杂度尽可能小,最好O(1)。
首先来分析一下这道题目,如果查询时间复杂度为O(1),那么只可能把这些数存入数组中,不错,这个想法非常正确,但是让我们来算一算需要多少内存。
一个int类型的数占4byte,1byte占8bit,所以一个int型的数占用4000000000*4byte = 16000000000byte = 15625000kb = 15258.8M = 14.9G
这谁能受得了,所以使用int型数组存储的话需要的内存太大,不能忍受,那么基于这种思路,使用char数组行不行呢,当然可以,一个char占用1byte,相当于使用原来1/4的内存空间,也就是大概3.7G,虽然小了不少,但是还是太大,因为这部分内存会被一直使用着,太浪费了。需要再想一想。
请看如下这幅图:
对,没错,就是使用位存储,把原来32个下标对应到一个int型数的32个位上边,然后再把这个整数对应到一个新的数组的下标上,相当于把原来的int型数组压缩为原来的1/32,那么这么算起来所占用的内存空间变为:4000000000*4byte/32 = 500000000byte = 488281k = 476.8M, 这个内存占用空间是可以接受的。
OK,思路分析完了,下面来看代码:
package test.array; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; public class BigArrayStorage { private static final String PATH = "file/int.txt"; private static final long LINE = 4000000000l; private static int[] array = new int[(int) (1 + LINE / 32)]; private static File FILE = new File(PATH); public static void main(String[] args) { long startInit = System.currentTimeMillis(); initFile(); long endInit = System.currentTimeMillis(); System.out.print("初始化需要的时间: "); System.out.println(endInit - startInit); // long startStore = System.currentTimeMillis(); // storeArray(); // long endStore = System.currentTimeMillis(); // System.out.print("存储需要的时间: "); // System.out.println(endStore - startStore); // // long startSearch = System.currentTimeMillis(); // System.out.print("查询到1999999994: "); // System.out.println(search(1999999994)); // long end1 = System.currentTimeMillis(); // System.out.print("查询1999999994所需要的时间: "); // System.out.println(end1 - startSearch); // // System.out.println("查询到2000000001: "); // System.out.println(search(2000000001)); // long end2 = System.currentTimeMillis(); // System.out.print("查询2000000001所需要的时间: "); // System.out.println(end2 - end1); } /** * 初始化文件,模拟4000000000个数字 */ public static void initFile() { try { FileWriter fileWriter = new FileWriter(FILE); for (long i = 0; i < LINE / 2; i++) { fileWriter.write(i + 1 + "\n"); } for (long i = 0; i < LINE / 2; i++) { fileWriter.write(i + 1 + "\n"); } fileWriter.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 把4000000000个数字转换存入数组中,这里采用按位存储,所以int型可以存32个下标,一个下标对应一位, 这就是压缩存储,因为设计的目的是只需要知道这个数字有没有在文件中出现 */ public static void storeArray() { try { FileReader fileReader = new FileReader(FILE); BufferedReader reader = new BufferedReader(fileReader); String value = null; while ((value = reader.readLine()) != null) { int intValue = Integer.parseInt(value); // 求在数组中的下标 int index = intValue / 32; // 求在32位中,使用哪一位来存储当前下标值 int position = intValue % 32; // 1左移position位是把第position位置为1,其它位为0 // “|”上当前数组值是不改变其它下标对应的值 array[index] = array[index] | (1 << position); } reader.close(); fileReader.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 查询目标值是否在数组中 * * @param target * 目标值 * @return true 查到, false 查不到 */ public static boolean search(int target) { // 求在数组中的下标 int index = target / 32; // 求在32位中,使用哪一位来存储当前下标值 int position = target % 32; int value = array[index]; // 当前值“&”上1左移position是为了获取第position为的值,此时把其它位都置为0了 value = value & (1 << position); // 无符号右移position位是为了把最后以为置为1,取得其十进制数判断是0或1 value = value >>> position; if (value == 1) { return true; } else { return false; } } } 初始化数组需要时间: 586396 然后修改一下main函数: public static void main(String[] args) { // long startInit = System.currentTimeMillis(); // initFile(); // long endInit = System.currentTimeMillis(); // System.out.print("初始化需要的时间: "); // System.out.println(endInit - startInit); long startStore = System.currentTimeMillis(); storeArray(); long endStore = System.currentTimeMillis(); System.out.print("存储需要的时间: "); System.out.println(endStore - startStore); long startSearch = System.currentTimeMillis(); System.out.print("查询到1999999994: "); System.out.println(search(1999999994)); long end1 = System.currentTimeMillis(); System.out.print("查询1999999994所需要的时间: "); System.out.println(end1 - startSearch); System.out.print("查询到2000000001: "); System.out.println(search(2000000001)); long end2 = System.currentTimeMillis(); System.out.print("查询2000000001所需要的时间: "); System.out.println(end2 - end1); }存储需要的时间: 289248 查询到1999999994: true 查询1999999994所需要的时间: 1 查询到2000000001: false 查询2000000001所需要的时间: 0 在内存中的数组,查询效率非常快,只需要O(1)的时间。内存占用也非常的少。到此,这个问题完美解决。