一、问题场景
对八位的无序电话号码文件进行排序,内存有限,不能将文件直接读取到内存中,所以不能使用冒泡、快速等算法进行排序,而硬盘有足够的空间,如何做?
二、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位数进行排序,可以多次读取文件,即使是一次读取,那么耗费内存的增长量来说,也是极其合算的,当内存有限制时,几乎无法对超大样本进行快速排序,此也即为位图排序的应用场景。(内存有限,而硬盘无限)
五、思考
如果文件中的样本不是每个取值唯一,而是有重复该如何使用位图排序?