编程珠玑上有道大数据的题,很有意思,今天把这个做了一下。
20亿的随机数字,查询某个数字在里面是否出现过。
可以直接用bitset存储20亿个数,大概要消耗内存600M左右。
可以用二分法思想:
最高位为1和为0的分成两组,然后次高位0,1又可以分成两组,依次类推。
我做了10000000万个数据的文件,刚开始很脑残的想把所有的数字都分类,这样查找就可以直接查了。1000万用二进制表示是100110001001011010000000 ,24位就可以表示了,所以创建一个D:/1/0/0/1/1/....
的目录,如果说这个目录存在,那么说明10000000这个数字是存在的。
程序进行了一会,大概半个小时吧。。。 突然意识到这要创建1000多万个目录。。。 停住。再想
在本题中,二分的目的是想把问题的规模缩小。因为海量的数据,用位图表示也是需要耗费大量内存的,所以用二分的形式把数据分成几个部分,查询的时候会耗费时间,但是消耗内存会小很多,就是一个时间换空间的想法。
比如说,把10000000个数字,分到d:/test/1.txt和d:/test/0.txt里,数字首位(24位表示)是1的在1.txt里,首位是0的在0.txt里。如果这样分还大,就再分次高位。把这些数字里首位是0,次高位也是0的放到d:/test/0/0.txt里,首位是0,次高位是1的放到d:/test/0/1.txt里,首位是1,次高位是0的放到d:/test/1/0.txt里,首位是1,次高位也是1的放到d:/test/1/1.txt里。 依次类推。
首先确定问题需要减小到什么规模。比如1000万个数字,我想把他分成万级别的数字文件处理,那么把他前十位作为文件路径,后面的位数作为值,存在相应的文件里。比如10000000,二进制表示为:100110001001011010000000 ,那么他的路径文件为:d:/test/1/0/0/1/1/0/0/0/1/0.txt,文件里面存的值为:01011010000000。
最终,把海量数据分成若干个含少量数据的文件。像上面我把1000万个数字的文件分成1000多个文件,每个文件里万级别的数字。分完以后就可以用位图把小文件里面的数字存进去查询了。
全局变量:
/** * 按位查找数组 */ static List<Integer> bitQuery = new ArrayList<Integer>(); static BitSet bit = new BitSet(0x1fff); /** * 初始化按位查找数组 */ static { // int i = 0x40000000; // int i = 0x800000; // 处理1000万,24位即可 while (i > 0) { bitQuery.add(i); i >>= 1; } } private static String dir = ""; private static String fileName = "";
其中,bitQuery是按位查询数组,用于判断某数字某一位是否为1,如bitQuery.get(0)=0x800000,他用于判断数字的首位是否为1(二进制24位表示)
二分法创建文件,并且把数字分到小文件中:
/** * @Title: initialize * @Description: 按照二分思想创建文件 * @return void 返回类型 */ private static void initialize() { FileWriter fw = null; PrintWriter pw = null; long start = System.currentTimeMillis(); Scanner s = null; int next = 0; File f = null; try { s = new Scanner(new File("d:/test.txt")); while (s.hasNext()) { next = s.nextInt(); getFileDirSmall(next); f = new File(dir); // System.out.println(dir); try { if (!f.exists()) { f.mkdirs(); } int fileName = (bitQuery.get(10) & next) == 0 ? 0 : 1; //next取后13位 next = next & 0x1fff; f = new File(dir + "/" + fileName + ".txt"); if (!f.exists()) { f.createNewFile(); } fw = new FileWriter(f, true); // 把数据追加进文件 pw = new PrintWriter(fw); pw.println(next); pw.flush(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { s.close(); pw.flush(); try { fw.flush(); fw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } long end = System.currentTimeMillis(); System.out.println("创建文件耗时" + (end - start) / 1000 + "秒"); }
根据要查询的数字找到相应小文件,用小文件里的数字初始化位图:
/** * 初始化bitset */ private static void installBitSet(int num) { getFileDirSmall(num); int fileName = (bitQuery.get(10) & num) == 0 ? 0 : 1; File f = new File(dir + "/" + fileName + ".txt"); System.out.println(dir + "/" + fileName + ".txt"); bit.clear(); if(!f.exists() || !f.isFile()){ return; } try { Scanner s = new Scanner(f); while (s.hasNext()) { bit.set(s.nextInt()); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
获取数字文件路径(脑残时候的想法,获取数字全部位数对应的文件路径。。):
/** * @Title: getFileDir * @Description: 获取数字应该在的文件路径 * @param number * @return * @return String 返回类型 */ private static String getFileDir(int number) { StringBuilder result = new StringBuilder("d:/test"); for (int i : bitQuery) { if ((number & i) == 0) { result.append("/" + 0); } else { result.append("/" + 1); } } dir = result.toString(); return null; }
获取数字应该在的文件路径(只处理前9位,把数字分成万级的小文件,因为第10为是文件名字):
/** * @Title: getFileDir * @Description: 获取数字应该在的文件路径(只处理前9位,把数字分成万级的小文件,因为第10为是文件名字) * @param number * @return * @return String 返回类型 */ private static String getFileDirSmall(int number) { StringBuilder result = new StringBuilder("d:/test"); for (int k = 0; k <= 9; k++) { int i = bitQuery.get(k); if ((number & i) == 0) { result.append("/" + 0); } else { result.append("/" + 1); } } dir = result.toString(); return null; }
主函数:
initialize(); System.out.println("输入要查找的数字:"); int i = 0; while (i != -1) { Scanner in = new Scanner(System.in); i = in.nextInt(); installBitSet(i); System.out.println((i & 0x1fff)); // System.out.println(Integer.toBinaryString(i)); // System.out.println(Integer.toBinaryString(0x1fff)); System.out.println(bit.get(i & 0x1fff)); }
结果:
创建文件耗时657秒 输入要查找的数字: 10 d:/test/0/0/0/0/0/0/0/0/0/0/0.txt 10 true 9 d:/test/0/0/0/0/0/0/0/0/0/0/0.txt 9 false 1451 d:/test/0/0/0/0/0/0/0/0/0/0/0.txt 1451 true 2312 d:/test/0/0/0/0/0/0/0/0/0/0/0.txt 2312 true 9999 d:/test/0/0/0/0/0/0/0/0/0/0/1.txt 1807 false
千万级别的文件二分为1024个小文件,耗时10分钟左右。
附件: 程序代码和1000万个数字