一、問題描述
有一個大文件,裏面有十億個字符串,亂序的,要求將這些字符串以字典的順序排好序
二、解決思路
將大文件切割成小文件,每個小文件內歸併排序;
對所有的小文件進行歸併排序——多重歸併排序
三、解決方案
3.1 模擬產生10億個隨機字符
public static void generateDate() throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(ORIGINALPATH));
Random random = new Random();
StringBuffer buffer = new StringBuffer(
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
int range = buffer.length();
int length = 1;
for (int i = 0; i < BIGDATALENGTH; i++) {
StringBuffer sb = new StringBuffer();
length = random.nextInt(20)+1;
//System.out.println("length--->"+length);
for (int j = 0; j < length; j++) {
//System.out.println("j--->"+j);
sb.append(buffer.charAt(random.nextInt(range)));
}
System.out.println("sb---->"+sb);
writer.write(sb.toString() + "\n");
}
writer.close();
}
3.2 對大文件進行切割
/**
* 將原始數據分成幾塊 並排序 再保存到臨時文件
* @throws IOException
*/
public static void splitData() throws IOException {
@SuppressWarnings("resource")
BufferedReader br = new BufferedReader(new FileReader(ORIGINALPATH));
tempFiles = new File[BIGDATALENGTH / TEMPFILELENGTH];//將會產生的臨時文件列表
for (int i = 0; i < tempFiles.length; i++) {
tempFiles[i] = new File(TEMPFILEPATH + "TempFile" + i + ".txt");
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFiles[i]));
HashMap<Integer,String> hashMap = new HashMap<Integer,String>();//未排序
//每次讀出TEMPFILELENGTH個文件 保存到smallLine中
for (int j = 1; j <= TEMPFILELENGTH; j++) {
String text = null;
if ((text = br.readLine()) != null) {
hashMap.put(j, text);
}
}
hashMap = MergeSort.sort(hashMap);
for(int k=1; k<=TEMPFILELENGTH; k++){
writer.write(String.valueOf(hashMap.get(k))
+ System.getProperty("line.separator"));
//System.getProperty("line.separator")相當於\n
}
writer.close();
}
}
3.3 對小文件進行遞歸歸併
/**
* 多路歸併排序
* @param files
* @throws IOException
*/
public static void multiWaysMergeSort(String[] files) throws IOException {
System.out.println("歸併文件-----第 "+mergeSortCount+" 次-----");
//當最後只有一個文件的時候 數據已經排序成功 直接複製保存到結果文件
if (files.length == 1) {
String lastFilePath = LASTFILEPATH + LASTFILENAME;
copyFile(files[0], lastFilePath, false);
//deleteFile(files[0]);
return;
}
for (int i = 0; i < files.length; i+=2) {
//開始合併兩個相鄰的文件 所以一次跳兩個
if (i == files.length - 1) {
//這時候已經只剩下最後一個文件了 不需要合併 本趟歸併結束
renameFile(files[i], i);
break;
}
//將br1 和 br2 寫入到Write
BufferedReader br1 = new BufferedReader(new FileReader(files[i]));
BufferedReader br2 = new BufferedReader(new FileReader(files[i + 1]));
BufferedWriter writer = new BufferedWriter(new FileWriter(TEMPFILEPATH + "last_" + mergeSortCount + "_" + i + ".txt"));
String s1 = br1.readLine();
String s2 = br2.readLine();
while (s1 != null || s2 != null) {
if (s1 != null && s2 != null) {
//都不爲空 纔有比較的必要
int mergeResult = s1.compareTo(s2);
if (mergeResult > 0) {//s1在s2後面
writer.write(s2);
writer.write(System.getProperty("line.separator"));
s2 = br2.readLine();
}
if (mergeResult == 0) {//s1=s2
writer.write(s1); writer.write(System.getProperty("line.separator"));
writer.write(s2); writer.write(System.getProperty("line.separator"));
//System.out.println("write time : " + writeTime++);
s1 = br1.readLine();
s2 = br2.readLine();
}
if (mergeResult < 0) {//s1在s2前面
writer.write(s1); writer.write(System.getProperty("line.separator"));
s1 = br1.readLine();
}
}
if (s1 == null && s2 != null) {
writer.write(s2);
writer.write(System.getProperty("line.separator"));
s2 = br2.readLine();
}
if (s2 == null && s1 != null) {
writer.write(s1);
writer.write(System.getProperty("line.separator"));
s1 = br1.readLine();
}
}
br1.close();
br2.close();
// deleteFile(files[i]);
// deleteFile(files[i + 1]);
writer.close();
}
mergeSortCount++;
multiWaysMergeSort(getTempFiles("last_" + (mergeSortCount-1) + "_"));
}
3.4 運行結果分析
①生成10億個隨機字符串,時間太久了,,字符串長度隨機在[1,20]之間時,文件大小大概在10.7 GB (11,500,161,591 字節)
② 切割成小文件,小文件內歸併排序,每個文件內的數據100萬條時,隨機選取五個排序時間如下:
一共發生了410832612 次對比一共發生了 899862656 次交換執行時間爲3545毫秒
一共發生了429506513 次對比一共發生了 940765504 次交換執行時間爲3512毫秒
一共發生了448181315 次對比一共發生了 981668352 次交換執行時間爲3497毫秒
一共發生了466856137 次對比一共發生了 1022571200 次交換執行時間爲3497毫秒
一共發生了485530473 次對比一共發生了 1063474048 次交換執行時間爲3981毫秒
總共1000個文件切割耗時爲
切割小文件所用時間--->4341734ms--->4341.734s--->72.36m--->1.206h
③ 小文件遞歸歸併,1000個文件,
共發生了10次歸併,
產生臨時文件總共1999個,
總大小爲127.8 GB (137,201,789,278 字節),
產生結果文件11.6 GB (12,500,161,591 字節)
比源文件多了10億個字節......
總耗時爲--->7374129ms--->7374.129s--->122.9m--->2.048h
不得不提的是,最後執行結果成功,也不枉我苦苦等待
四、相關技術
4.1 歸併排序
排序原理不多介紹,各種到處都有,如果一時不記得,看下面的原理圖。秒懂。
4.2 文件讀寫
本程序很重要的一點就是對於文件的讀寫,Buffer的文件讀寫可以很大程度的改善速率
寫操作:
BufferedWriter writer = new BufferedWriter(new FileWriter(PATH));
writer.write("hhf\n");
讀操作:
BufferedReader br = new BufferedReader(new FileReader(PATH));
text = br.readLine()
五、關於優化
5.1分小文件時優化
前提:數據均勻,保證每個小文件大小不會超過內存的容量
處理:在分數據到小文件時,按字符串按首字母將其分到指定文件中,如A-C分配到1.txt,D-F分配到2.txt.......
優點:只需要小文件內數據排序,排序號後,即可將1.txt、2.txt、3.txt直接連接起來,極大的縮短了歸併時間,相當於把遞歸歸併變成了文件連接而已
缺點:前提不是很容易把握,若有一個小文件內的數據量大於內存的大小,則排序失敗,存在一定的風險
5.2小文件內排序時優化
前提:保證每個小文件內數據量比較不是特別的大
處理:將小文件內的數據進行快速排序
優點:快排的時間效率是高於歸併的
以下是測試數據
排序數量級 10 1000 100000
歸併排序7ms 71ms 3331ms
快速排序6ms 52ms java.lang.StackOverflowError
缺點:缺點已經顯示在測試數據內了,小文件內的數據量過大就可能導致當前線程的棧滿
(附上源代碼工程:Merge.zip)