10億個字符串的排序問題

一、問題描述

有一個大文件,裏面有十億個字符串,亂序的,要求將這些字符串以字典的順序排好序

 

二、解決思路

        將大文件切割成小文件,每個小文件內歸併排序;

        對所有的小文件進行歸併排序——多重歸併排序

 

三、解決方案

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)

發佈了30 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章