`

归并算法在大文件处理中的使用

阅读更多

本文描述了一下归并算法在大文件处理中的使用.

应用场景:

1.单个文件,大小>机器内存,对文件数据进行排序(顺序,小->大)

2.单个文件,大小>机器内存,对文件数据进行去重

简单描述一下大文件排序的思路

1.文件拆分

2.拆分后的小文件分别排序,为之后的归并排序做准备

3.归并排序,这里是核心.首先,因为小文件已经排好序了,那么接下来要做的就是将有序的小文件进行合并,生成一个有序的结果文件.大概流程如下:

设置所有小文件从第一行开始读取,一次又一次的循环,循环里做的事很简单,每次循环,读取所有小文件的一行数据(如何决定读取第几行?请看下面的描述),将这些数据存入有序的list中,然后在list中取最小值,并记录索引,将最小值写入结果文件,同时标记对应的小文件,那么下一次循环时,该小文件则读取第2行,其他小文件依然读取第1行.很显然,就是每次循环,找到当前一轮读取的数据中的最小值,写入结果文件,同时将对应的小文件读取索引+1.直到所有小文件都读完最后一行数据,那么归并操作也就结束了.这里要说明的是,由于小文件已经是有序的,所以只需要在每次循环中找到当前所读数据中最小的值即可保证结果文件的有序.

关于去重,如果完全理解了排序的思路,那么对一个排好序的文件去重就相对简单了.

以上只是简单描述归并算法的使用,并未关注性能.所以接下来的实现,只是基于测试的目的,亲测后,性能是很差滴,筒子们,还是转动大脑,各显神通吧.

最后,附上 大文件排序 的代码

 OperateFile.java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;


/**
 * 文件处理
 */

public class OperateFile {
	public static Integer UNITCOUNT = 100000;//单元数据条数
	private static Integer RANDOMSCOPE = 10000;//随机取数范围
	public static String sortpath="/tmp/sort";
	public static String splitpath="/tmp/sort/split";
	private static Random random = new Random();//
	/**
	 * 创建文件
	 * @param count
	 * @param path
	 * @throws Exception
	 */
	public void createFile(Long count,String path) throws Exception{
		BufferedWriter writer = new BufferedWriter(new FileWriter(path,false));
		Long i = 0l;
		while(i<count){
			StringBuffer tmp = new StringBuffer();
			//取2个随机数进行append组合成一个随机long值
			tmp.append(random.nextInt(RANDOMSCOPE)).append(random.nextInt(RANDOMSCOPE));
			writer.write(tmp.toString());
			writer.write("\n");
			i++;
		}
		if(writer != null){
			writer.flush();
			writer.close();
		}
	}
	/**
	 * 分割文件
	 * @param path
	 * @throws Exception
	 */
	public void splitFile(String path) throws Exception{  
		File file = new File(splitpath);
		File[] files = file.listFiles();
		for (int i = 0, n = files.length; i < n; i++) {
			files[i].delete();
		}
		BufferedReader reader = new BufferedReader(new FileReader(path));
		String tmp = null;
		StringBuffer content = new StringBuffer();
		int i = 0;
		int sum = 0;
		List<Integer> list = new LinkedList<Integer>();
		while((tmp=reader.readLine()) != null){
			if("".equals(tmp)) continue;
			list.add(Integer.parseInt(tmp));
			//如果等于或超出单元条数,则通过集合工具类排序,并生成1个新的分割文件,这里的排序,只是为后面的归并sort做准备,真实场景中肯定需要自己来处理排序
			if(list.size()>=UNITCOUNT){
				Collections.sort(list);
				for (int j = 0, n = list.size(); j < n; j++) {
					content.append(list.get(j));
					content.append("\n");
				}
				createSmallFile(content.toString(), splitpath+"/splittmp."+(i++));
				content.setLength(0);
				list.clear();
			}
			sum++;
		}
		System.out.println(sum);
		System.out.println(list.size());
		//最后还会剩下一些未达到单元条数,但又未spill到磁盘的数据,依然要生成新的分割文件
		if(list.size()>0){
			Collections.sort(list);
			for (int j = 0, n = list.size(); j < n; j++) {
				content.append(list.get(j));
				content.append("\n");
			}
			createSmallFile(content.toString(), splitpath+"/splittmp."+(i++));
			content.setLength(0);
			list.clear();
		}
		if(reader != null){
			reader.close();
		}
	}
	/**
	 * 生成分割文件
	 * @param content
	 * @param path
	 * @throws Exception
	 */
	private void createSmallFile(String content, String path) throws Exception{
		BufferedWriter writer = new BufferedWriter(new FileWriter(path,false));
		writer.write(content);
		if(writer != null){
			writer.flush();
			writer.close();
		}
	}


	public static void main(String[] args) {
		OperateFile operateFile = new OperateFile();
		try {
			long start = new Date().getTime();
			operateFile.createFile(10000000l, sortpath+"/bigfile.txt");
			long end = new Date().getTime();
			System.out.println("createFile:"+(end-start)/1000+"秒");
			start = end;
			operateFile.splitFile(sortpath+"/bigfile.txt");
			end = new Date().getTime();
			System.out.println("splitFile:"+(end-start)/1000+"秒");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 BigFileSort.java

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

public class BigFileSort {
	private static Integer WRITEUNITCOUNT = 1;//100M
	private static Integer READUNITCOUNT = 1000;//100M
	private StringBuffer finish = new StringBuffer();
	private BufferedWriter writer = null;
	public static Map<String, List<Long>> splitMap = null;//split已读数据的缓存
	public static Map<String, BufferedReader> readers = null;
	/**
	 * 归并排序
	 * @param files
	 * @param target
	 * @throws Exception
	 */
	public void sortBatch(String[] files, String target) throws Exception{
		int fn = files.length;
		//存放每个split文件读取的当前行数下标
		HashMap<String,Integer> indexMap = new LinkedHashMap<String, Integer>();
		for (int i = 0; i < fn; i++) {
			indexMap.put(files[i], 0);//初始从0开始读取
		}
		while(true){
			//存放当前循环每个split文件参与排序的数据
			List<Long> list = new LinkedList<Long>();
			//存放当前循环需要进行归并操作的文件
			List<String> indexStore = new LinkedList<String>();
			int cnt = 0;
			for(String file : indexMap.keySet()){
				if(indexMap.get(file)!=null){//如果当前split文件的处理索引为null,说明该split文件的数据已全部归并完成
					//根据文件地址和已读行数来获得当前所读行的值
					Long val = readContent(file, indexMap.get(file));
					//此处的约定是,如果为null,则认为已经超出split文件行数的索引范围,视为当前文件的读已经over了,那下一轮,不再被进行归并了,因为它已经被归并完了.
					//反复几轮以后,所有split文件都会被归并,整体归并也就over了
					if(val == null){//如果返回值为null,则被认为是该split文件的数据已全部归并完成
						indexMap.put(file, null);
						cnt++ ;
					}else{
						indexStore.add(file);
						list.add(val);
					}
				}else{
					cnt++ ;
				}
			}
			if(cnt == fn) break;
			int tmpIndex = 0;
			int n = list.size();
			for (int i = 0; i < n; i++) {
				//进行比较,设置最小值的索引
				if(list.get(tmpIndex)>list.get(i)) tmpIndex=i;
			}
			try {
				//根据最小值对应的索引,获得其所在的split文件,并追加写入结果文件
				write(list.get(tmpIndex).toString()+"\n", target, finish.toString().split("\n").length>=WRITEUNITCOUNT?true:false);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//更新最小值所在split文件的已读取行数
			indexMap.put(indexStore.get(tmpIndex), indexMap.get(indexStore.get(tmpIndex))+1);
		}
		if(writer != null){
			writer.flush();
			writer.close();
		}
	}
	
	/**
	 * 读取一行数据
	 * @param path
	 * @param index
	 * @return
	 * @throws Exception
	 */
	public Long readContent(String path,int index) throws Exception{
		List<Long> list = splitMap.get(path);//获得split文件读缓存
		int cnt = index/READUNITCOUNT;//整除取倍数,这里需要说明的是,为了减少频繁打开split文件所消耗的性能,此处通过临时缓存的方式来保存一次性读取的数据(缓存大小可以根据split文件数和mem大小来设置)
		if(index%READUNITCOUNT==0){//当前索引正好为整除倍数,则清空临时缓存,加载下一批数据
			BufferedReader reader = new BufferedReader(new FileReader(path));
			list.clear();
			String tmp = null;
			int i = 0;
			while((tmp=reader.readLine()) != null){
				if(i/READUNITCOUNT>cnt){//如果超出当前批次需要加载的数据,则break
					break;
				}
				if(i/READUNITCOUNT==cnt)//如果在当前批次需要加载的数据范围内,则添加至临时缓存
					list.add(Long.parseLong(tmp));
				i++;
			}
			if(reader != null) reader.close();
		}
		Long res = null;
		try {
			if(list.size()>0)//如果临时缓存中,没有数据,则返回null
				res = list.get(index%READUNITCOUNT);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return res;
	}
	
	public void write(String count,String path,boolean isWrite) throws Exception{
		finish.append(count);//append到buffer
		if(isWrite){//将buffer区域push到文件
			if(writer==null) writer = new BufferedWriter(new FileWriter(path,true));
			writer.write(finish.toString());
			finish.setLength(0);
		}
	}
	
	public static void main(String[] args) {
		BigFileSort fileSort = new BigFileSort();
		try {
			File parent = new File(OperateFile.splitpath);
			String[] files = parent.list(new FilenameFilter() {
				public boolean accept(File dir, String name) {
					if(name.indexOf("splittmp") != -1)
						return true;
					return false;
				}
			});
			splitMap = new HashMap<String, List<Long>>();
			for (int i = 0; i < files.length; i++) {
				files[i] = OperateFile.splitpath+File.separator+files[i];
				splitMap.put(files[i], new ArrayList<Long>());
			}
			File f = new File(OperateFile.sortpath+"/sort.res");
			f.delete();
			long start = new Date().getTime();
			fileSort.sortBatch(files, OperateFile.sortpath+"/sort.res");
			long end = new Date().getTime();
			System.out.println((end-start)/1000+"秒");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

分享到:
评论

相关推荐

    Python-文件夹归并算法前提文件夹里面的数据都是有序的

    文件夹合并算法(处理17亿条数据,120个文件,总共5-80G文件的有序合并只需要6.5小时,单线程)

    完整视频-coursera公开课 普林斯顿算法 ⅠⅡ部分

    视频一个两部分,算法(一)主要集中在基础的数据结构、排序、查找算法。 相关主题有:并查集算法,二分查找,栈,队列,背包,插入排序,选择排序,希尔排序,快速排序, 三切分快排,归并排序,堆排序,二分堆,二...

    【华科复试】【贪心算法】最优二路归并树&二路归并排序

    为得到归并树根结点表示的归并文件,外部结点中每个文件记录需要移动的次数=该外部结点到根的距离,即根到该外部结点路径的长度,如:下列F4在整个归并过程中的移动量为4。 带权外部路径长度:记di是由根到代表...

    算法:算法C语言实现 第1-4部分 基础知识、数据结构、排序及搜索

    第二部分“数据结构”(第3~5章)讲解算法分析中必须掌握的数据结构知识,主要包括基本数据结构、抽象数据结构、递归和树。第三部分“排序”(第6~11章)按章节顺序分别讨论基本排序方法(如选择排序、插入排序、...

    数据结构与算法.xmind

    Rabin fingerprints 文件指纹算法 BitMap 位图算法 BloomFilter 布隆过滤器 线性表 栈 先进后出(LIFO, Last In First Out) 实现 用两个指针域分别指向栈顶和栈底 常见操作 进栈 栈顶...

    《C算法》((美国)Robert Sedgewick)清晰版[DJVU] 第一卷

    第二部分 “数据结构”(第3~5章)讲解算法分析中必须掌 握的数据结构知识。主要包括基本数据结构、抽象数据结构、递归和树。第三部分“排序”(第6~11章 )按章节顺序分别讨论了基本排序方法(如选择排序、插入...

    《C算法》((美国)Robert Sedgewick)清晰版[DJVU] 第二卷

    第二部分 “数据结构”(第3~5章)讲解算法分析中必须掌 握的数据结构知识。主要包括基本数据结构、抽象数据结构、递归和树。第三部分“排序”(第6~11章 )按章节顺序分别讨论了基本排序方法(如选择排序、插入...

    Java数据结构和算法中文第二版(1)

    利用数据结构和算法为现实世界的处理过程建模 了解不同的数据结构的优势和弱点,考虑如何利用它们改进编程的效率 学会如何用面向对象的编程简化数据结构和算法 本书以一种易懂的方式教授如何安排和操纵数据的问题...

    数据结构习题答案(全部算法)严蔚敏版

    6.4.3 中根次序线索化算法 6.4.4 在中根线索树上检索某结点的前趋或后继 6.4.5 在中根线索树上遍历二叉树 6.5 二叉树、 树和森林 6.5.1 树的存储结构 6.5.2 树与二叉树之间的转换 6.5.3 森林与二叉树的转换 ...

    Java数据结构和算法中文第二版(2)

    利用数据结构和算法为现实世界的处理过程建模 了解不同的数据结构的优势和弱点,考虑如何利用它们改进编程的效率 学会如何用面向对象的编程简化数据结构和算法 本书以一种易懂的方式教授如何安排和操纵数据的问题...

    9种排序方法及python实现(冒泡,插入,希尔,选择,堆,快速,桶,基数,归并排序)

    1. 排序算法分类 ...而后在归并阶段将这些临时文件组合为一个大的有序文件,也即排序结果。 (2)内部排序还可以细分: 需要额外内存空间(out-place,即空间复杂度不是O(1)O(1)O(1))的算法有:桶排序,基数

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    *3.4.2 在输入流与输出流中使用控制符 3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 ...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    *3.4.2 在输入流与输出流中使用控制符 3.4.3 用getchar和putchar函数进行字符的输入和输出 3.4.4 用scanf和printf函数进行输入和输出 3.5 编写顺序结构的程序 3.6 关系运算和逻辑运算 3.6.1 关系运算和关系表达式 ...

    C语言常用算法

    048 归并排序 049 基数排序 050 二叉搜索树操作 051 二项式系数递归 052 背包问题 053 顺序表插入和删除 054 链表操作(1) 055 链表操作(2) 056 单链表就地逆置 057 运动会分数统计 058 双链表 059 ...

    [数据结构(C语言版)].严蔚敏_吴伟民.高清扫描版.rar

    其内容和章节编排 1992年4月出版的《数据结构》(第二版)基本一致,但在本书中更突出了抽象数据类型的概念。全书采用类C语言作为数据结构和算法的描述语言。 本书概念表述严谨,逻辑推理严密,语言精炼,用词达意,...

    数据结构考研纲要

    归并需要使用较多空间用于元素复制 (2)直插、冒泡有序时O(n);平均和最坏O() (3)简单选择最差:O() ;有序情况比较次数不变;但是不移动;最坏3(n-1)次移动 (4)堆排序O(n);无论时间、空间;数据各方面最好;平均...

Global site tag (gtag.js) - Google Analytics