`

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

阅读更多

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

应用场景:

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();
		}
	}
}

 

分享到:
评论

相关推荐

    多路归并算法

    通过以上步骤,我们可以利用多路归并算法在C#中实现对文件数据的排序,并将排序结果保存到新的文件中。这种算法在处理大量数据时具有较高的效率,特别是当内存不足以容纳全部数据时,通过合理地使用磁盘空间,多路...

    归并算法实现源码

    归并排序(Merge Sort)是一种基于分治策略的高效排序算法,它的主要思想是将待排序的序列分为两部分,分别进行排序,...在实际编程中,了解并掌握归并排序可以帮助你更好地处理大规模数据的排序问题,提高程序的性能。

    归并算法的代码

    在提供的`Src`文件中,可能包含了使用特定编程语言(如Python、Java、C++等)实现的归并排序代码,新手可以通过阅读和理解这些代码来学习归并排序的工作原理和实现细节。 归并排序的时间复杂度是O(n log n),空间...

    归并分类算法、改进的归并分类算法和快速分类算法.zip

    归并排序(Merge Sort)、改进的归并排序(Improved Merge Sort)和快速排序(Quick Sort)是计算机科学中三种常见的排序算法,它们在处理大量数据时具有高效性和稳定性。这三种算法各有特点,适用于不同的场景。 ...

    改进的归并排序算法

    在`improved_merge_sort.cpp`文件中,我们可以期待看到如何实现这两种改进的归并排序方法。代码可能包含了如何避免回写以及如何使用循环结构替代递归的具体实现细节。分析和理解这段代码可以帮助我们深入理解归并...

    归并排序算法实现(排序算法系列1)

    在给定的压缩包文件中,"归并排序"可能是实现这个算法的源代码文件。通过查看和分析这段代码,我们可以更深入地理解归并排序的内部机制,学习如何在实际编程项目中应用这种排序算法。 总结来说,归并排序是一种分治...

    C++实现希尔、快速、堆排序、归并排序算法

    在编程领域,排序算法是计算机科学中的重要组成部分,特别是在数据处理和算法效率分析上。本文将详细介绍C++中实现的希尔排序、快速排序、堆排序和归并排序这四种经典排序算法。 希尔排序,由Donald Shell于1959年...

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

    标题和描述提到的"Python-文件夹归并算法前提文件夹里面的数据都是有序的"以及"文件夹合并算法(处理17亿条数据,120个文件,总共5-80G文件的有序合并只需要6.5小时,单线程)",主要涉及到的是一个优化的归并排序...

    快速排序与归并排序的算法比较实验报告

    这篇实验报告将深入探讨两种经典的排序算法——快速排序和归并排序,通过对它们在Java环境中的实现和性能测试,揭示它们在处理不同规模数据时的效率差异。 **快速排序(Quick Sort)** 快速排序由C.A.R. Hoare在...

    归并分类 计算机算法 c/c++语言 递归和非递归

    虽然它不是原地排序,但其稳定性(保持相等元素的相对顺序)和高效的性能使得它在大数据集和外部排序中很有用。 总的来说,归并排序是计算机科学中的一个重要概念,理解和掌握其递归和非递归实现对于任何程序员来说...

    归并算法python注释详细.zip

    你可以直接在PyCharm中打开归并算法的Python文件,进行编辑、运行和调试。 5. **Python作为开发语言**:Python以其简洁明了的语法和丰富的库支持而广受欢迎,特别适合教学和学习算法。在归并排序的实现中,Python的...

    归并排序算法实现

    在给出的文件名列表中,"归并排序算法.VC.VC.opendb"、"归并排序算法.sln"、"Debug"、".vs"、"归并排序算法"和"归并排序算法.VC.db"都是与Visual Studio项目相关的文件,可能包含了一个使用C++实现的归并排序算法的...

    快速排序与归并排序比较(C++).rar_c++paixusuanfa_归并_归并排序_归并排序算法

    快速排序和归并排序是两种常用的排序算法,它们在计算机科学和编程中有着广泛的应用。本文将详细讨论这两种排序算法的原理、实现方式以及性能对比。 快速排序是一种由C.A.R. Hoare在1960年提出的分治算法。其基本...

    matlab 快速排序和归并排序算法

    快速排序和归并排序是两种常用的排序算法,它们在计算机科学和编程领域有着广泛的应用,尤其是在数据处理和分析中。MATLAB作为一种强大的数值计算和数据分析工具,提供了丰富的函数和工具来实现这些算法。 快速排序...

    数据结构 排序算法之归并排序

    在`MergeSort.cpp`文件中,我们可以期待看到归并排序的C++实现。通常,C++代码会包含以下几个关键部分: - `merge()`函数:用于合并两个已经排序的子序列。这个函数通常使用两个指针分别遍历两个子序列,比较它们的...

    数据结构与算法在实际项目当中的运用

    例如,如果项目涉及大量数据的处理,可以考虑使用分布式数据结构,如Hadoop的分布式文件系统HDFS,以及MapReduce模型进行大数据处理。对于实时性要求高的系统,可能需要借助内存数据结构,如Redis中的数据结构,以...

    编程实现希尔、快速、堆、归并排序算法,随机产生10000个随机数存入磁盘文件,读取,并将不同排序方法的结果存入文件中(含报告)

    这样做的目的是为了测试各种排序算法在大规模数据上的表现,并通过比较结果文件来分析它们的性能差异。 此外,项目还包括了一份报告,该报告可能涵盖了以下几个方面: - 实验目的与背景 - 排序算法的原理介绍 - ...

    JAVA归并排序算法.pdf

    JAVA 归并排序算法 JAVA 归并排序算法是利用 "归并 "技术来进行排序的。归并是指将若干个已排序的子文件合并成一个有序的文件。...JAVA 归并排序算法是一种高效的排序算法,适用于大规模数据排序。

    《算法4》jar文件

    使用这两个jar文件,开发者可以方便地在Java项目中复用《算法4》中的成熟算法,而无需从头开始编写代码。这不仅提高了开发效率,也便于学习和理解这些经典的算法思想。无论是初学者还是经验丰富的开发者,都可以从中...

    VC++2012编程演练数据结构《35》多路平衡归并

    项目中的"35.cpp"文件很可能是实现多路平衡归并算法的主要代码文件,包含函数定义和主程序逻辑。"StdAfx.cpp"和"StdAfx.h"是Visual C++项目特有的预编译头文件,用于提高编译速度,通常包含常量定义、通用函数和...

Global site tag (gtag.js) - Google Analytics