位图查询海量数字是否存在问题

       编程珠玑上有道大数据的题,很有意思,今天把这个做了一下。

       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万个数字

猜你喜欢

转载自709002341.iteye.com/blog/2257708