位图排序——《编程珠玑》学习笔记

一、问题场景

对八位的无序电话号码文件进行排序,内存有限,不能将文件直接读取到内存中,所以不能使用冒泡、快速等算法进行排序,而硬盘有足够的空间,如何做?

二、Java代码实现

package demo.sort;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import demo.time.Timer;

public class BitMapSort {
	
	private byte[] baseByte =new byte[]{
			0x1,
			0x2,
			0x4,
			0x8,
			0x10,
			0x20,
			0x40,
			(byte) (0x80 ) };

	public static final int BYTE_LENGTH = 8;
	/** 取值范围:最大值减最小值 +1 */
	private int max_sub = 0;
	/** 取值开始值 */
	private int num_start = 0;
	@Test
	public void test() {
		
		sortByBitMap("F:/ProgrammingPearls/randnum1.txt",
				"F:/ProgrammingPearls/sort3.txt", 10000000, 90000000);
	}
	/**
	 * 
	 * @param sourcesName 待排序文件
	 * @param targetName  目标文件
	 * @param start_num   开始的数字
	 * @param range       数字取值范围,最大-最小+1
	 */
	public void sortByBitMap(String sourcesName, String targetName, int start_num, int range){
		setNum_start(start_num);
		setMax_sub(range);
		Timer timer = new Timer();
		byte[] sortMap = initBitMap(sourcesName, range/ BYTE_LENGTH);
		timer.printRunTime();
		printToFile(targetName,sortMap);
		timer.printRunTime();
	}
	
	private void printToFile(String outputFileName , byte[] content ){
		//System.out.println("111\n".replaceAll("\\D", "") +"--------");
		File file = new File(outputFileName);
 		FileWriter fileWritter;
 		BufferedWriter bufferWritter;
		try {
			fileWritter = new FileWriter(file,true);
			bufferWritter = new BufferedWriter(fileWritter);
			
			for(int i =0; i< content.length;i++){
				//System.out.println("sortMap[i]="+sortMap[i]);
				int[] nums = getSortFromByte(content[i] );
				for(int temp: nums){
					int sortElement = num_start + temp + i * BYTE_LENGTH;
					 try {						
							bufferWritter.write(sortElement + "\n\r");							
				    	} catch (IOException e1) {
							// TODO Auto-generated catch block
							e1.printStackTrace();
						}   
					//System.out.println(sortElement);
				}
			}
			bufferWritter.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private byte[] initBitMap(String path, int count){
		//String path = "F:/ProgrammingPearls/randnum1.txt";
		File file = new File(path);
		FileReader in = null;
		LineNumberReader reader = null;
		try {
			in = new FileReader(file);
			reader = new LineNumberReader(in);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
		byte[] sortMap = new byte[count];
		String numStr = "";
		while(numStr != null){
			try {
				numStr = reader.readLine();
				recordToByte(sortMap, numStr);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return sortMap;
	}
	private void recordToByte(byte[] bytes, String numStr){
		if(numStr == null || numStr.equals("")){
			return;
		}
		numStr = numStr.replaceAll( "\\D", "");
		//System.out.println("----->>numStr="+numStr);
		try{
			final int num = Integer.parseInt(numStr);
			int index = (num- num_start )/ BYTE_LENGTH ; 
			int bitIndex = num % BYTE_LENGTH;
			bytes[index] = recordToByte(bytes[index], bitIndex);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private byte recordToByte(final byte bit, final int bitindex){
		
		byte result = bit;
		if(bitindex >= 0 && bitindex < 8){
			result = (byte) (bit | baseByte[bitindex]);			
		}
		return result;
	}
	
	private int[] getSortFromByte(final byte bit ){
//		int[] result = new int[8]{-1};
		List<Integer> result = new ArrayList<Integer>();
		for(int i=0;i< 8;i++){
			if( (bit & baseByte[i]) != 0){
				result.add(i);
			}
		}
		int[] result2 = new int[result.size()];
		for(int i =0; i <result2.length ;i++ ){
			result2[i] = result.get(i);
		}
		
		return result2;
	}

	public int getMax_sub() {
		return max_sub;
	}

	public void setMax_sub(int max_sub) {
		this.max_sub = max_sub;
	}

	public int getNum_start() {
		return num_start;
	}

	public void setNum_start(int num_start) {
		this.num_start = num_start;
	}
}

 
 

三、排序耗时

初始化位图运行时间为:875.0毫秒
将位图写入排序文件运行时间为:1043.0毫秒

合计耗时:1913毫秒

计算机处理器:i5 

四、占用内存

文件中共有一百万个八位整数,从1000 0000 到9999 9999,共计有9000 0000个数字,申请一个1125 0000个byte数组 ≈ 10.73MB

如果使用内存排序(如冒泡、快速等)需要 100 0000 * 4B ≈ 3.814MB,请注意,这里的数据量只有一百万个,但是数字有九千万种可能,位图排序如果是全集的话,仍然只需要10M多的内存,但是内存排序却需要 九十倍,也就是270M的存储内存,这里并不考虑时间上的效率。也就是每个数字,需要消耗4B内存(int型),当在此例中,文件中的无序数字为 2812500个时,内存排序所需空间,要超过位图排序。请注意,此时的两百万只占九千万的 2%多一些,当样本数进一步增大时, 其消耗的内存会进一步成倍增长。

如果排序的样本不是八位数,而是9位 10位呢?很显然,内存排序根本吃不消,但是位图排序,却可以在多次扫描文件的方式实现,其内存消耗几乎不增长。

此处,可见位图排序的优势所在

1. 内存消耗有限,与样本数无关,与取样范围有关(内存少

2. 耗时只花在读文件和写文件时,排序几乎没有耗费时间(耗时少

3. 如果是对9位数 10位数进行排序,可以多次读取文件,即使是一次读取,那么耗费内存的增长量来说,也是极其合算的,当内存有限制时,几乎无法对超大样本进行快速排序,此也即为位图排序的应用场景。(内存有限,而硬盘无限


五、思考

如果文件中的样本不是每个取值唯一,而是有重复该如何使用位图排序?


你可能感兴趣的:(java,排序,内存,位图)