海量数据排序

59 篇文章 8 订阅
42 篇文章 0 订阅
  排序分为内部排序和外部排序两大类,若要排序的元素很多,整个序列的排序过程不可能在内存中完成,则此类排序就称为外部排序。针对外部排序这种问题,下面我们来介绍一种方法。
  由于要排序的数据量很大,不可能一次性加载到内存,所以我们就采取"分而治之"的方法。从大文件中读取一部分数据加载到内存进行快速排序,再将有序的序列再放回到小文件中。最后再使用归并排序的思想,对这些小文件进归并处理,使得最后只剩下一个文件。则最后的这个文件里面记录的就是排好序的数据。

算法流程:




举个栗子:

问:有40亿个整数,只有1G的内存,对这些整数进行排序。

分析:
  40个整数有160亿个字节,也就是16G的内存才能放下,所以直接加载到内存中排序是不现实的,我们可以将这40亿个整数放到20个小文件中,平均每个小文件是0.8G大约800M左右。我们分别将每个文件都加载到内存中对他们使用快速排序,然后再将排序好的文件写入到文件中。等到这20个小文件都全部有序的时候,我们再对这些小文件进行归并排序,最后将这20个小文件合并成一个文件,这样就将这40亿个整数排好序了。

代码实现:
#pragma once
#include<vector>
#include<cassert>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
using namespace std;

class BigDataSort
{
public:
	BigDataSort(char * bigfile, int n, size_t limit)    //n表示要切分成多份少小文件
		:_bigfile(bigfile)                              //bigfile表示要排序的文件名
		, _limit(limit)                                 //_limit代表小文件中最多存放的数据个数
	{
		_SmallFileName.resize(n);
		for (int i = 0; i < n;++i)                      //先创建n个文件名
		{
			char name[128] = {0};
			_itoa(i,name,10);
			_SmallFileName[i] = name;
		}
	}

	void Sort()
	{
		SmallFileSort();            //小文件排序
		SmallFileMerge();           //小文件归并
	}
protected:

	void SmallFileSort()           //将大文件分成多份,排好序之后再写入小文件
	{
		FILE *fRead = fopen(_bigfile, "rb");           //打开大文件
		assert(fRead);
		for (size_t i = 0; i < _SmallFileName.size(); ++i)
		{
			vector<int> v;
			for (size_t count = 0; count < _limit; ++count)    //将这些小文件中的数据加载到内存
			{
				int num = 0;
				if (_GetNum(fRead, num))               //如果读取成功,则将它放到vector中
					v.push_back(num);
				else
					break;
			}

			//对这些数据进行快速排序
			sort(v.begin(),v.end());
	
			//将这些数据再写到文件中
			FILE *fWrite = fopen(_SmallFileName[i].c_str(), "wb");
			assert(fWrite);
			for (size_t i = 0; i <v.size(); ++i)       //将这些小文件中的数据加载到内存
			{
				_InputNum(fWrite,v[i]);         //向文件中写入一个数
			}
			fclose(fWrite);
		}
		fclose(fRead);
	}



	//对这些文件进行归并排序
	void SmallFileMerge()
	{
		while (_SmallFileName.size()>1)       //将这些小文件最后合并的只剩一个文件
		{
			vector<string> v;
			while (_SmallFileName.size() > 1)
			{
				string filename1 = _SmallFileName.back();
				_SmallFileName.pop_back();
				string filename2 = _SmallFileName.back();
				_SmallFileName.pop_back();

				FILE *fRead1 = fopen(filename1.c_str(), "rb");
				assert(fRead1);
				FILE *fRead2 = fopen(filename2.c_str(), "rb");
				assert(fRead2);

				string newfile = filename1 + filename2;     //创建一个新的文件名,用来存放这两个文件合并后的结果
				v.push_back(newfile);                       //将新文件保存起来

				FILE* fWrite = fopen(newfile.c_str(), "wb");
				assert(fWrite);

				_Merge(fRead1,fRead2,fWrite);               //对这两个文件中的数据进行归并排序

				fclose(fRead1);
				fclose(fRead2);
				fclose(fWrite);

				remove(filename1.c_str());                   //删除这些已经合并过的辅助文件
				remove(filename2.c_str());
			}
			for (size_t i = 0; i < v.size(); ++i)
			{
				_SmallFileName.push_back(v[i]);
			}
		}

		//将最后合并的文件再拷贝回原文件中
		FILE* fWrite = fopen(_bigfile,"wb");
		assert(fWrite);

		string filename = _SmallFileName[0];
		FILE* fRead= fopen(filename.c_str(), "rb");
		assert(fRead);

		string line;
		while (_GetLine(fRead,line))
		{
			line += '\n';
			fputs(line.c_str(), fWrite);
			line.clear();
		}	
		fclose(fWrite);
		fclose(fRead); 
		remove(filename.c_str());             //删除辅助文件
	}

protected:
	void _Merge(FILE* fRead1,FILE* fRead2,FILE* fWrite)        //将小文件进行合并
	{
		int num1 = 0;
		int num2 = 0;
		bool file1 = _GetNum(fRead1,num1);
		bool file2 = _GetNum(fRead2,num2);
		while (file1!=false&&file2!=false)          //归并排序 
		{
			if (num1<num2)
			{
				_InputNum(fWrite,num1);         //向文件中写入一个数
				file1 =_GetNum(fRead1, num1);
			}
			else
			{
				_InputNum(fWrite, num2);
				file2 = _GetNum(fRead2, num2);
			}
		}

		while (file1==true)
		{
			_InputNum(fWrite, num1);         //向文件中写入一个数
			file1 = _GetNum(fRead1, num1);
		}

		while(file2==true)
		{
			_InputNum(fWrite, num2);         //向文件中写入一个数
			file2 = _GetNum(fRead2, num2);
		}
	}

protected:
	bool _GetNum(FILE *fRead,int& num)  //读取一个整数,并将整数通过num返回
	{
		int ch = 0;
		string line;
		while ((ch = getc(fRead)) != EOF&&ch != '\n')
		{
			line += ch;
		}
		if (ch == EOF)
			return false;
		else
		{
			num = atoi(line.c_str());
			return true;
		}
	}

	bool _GetLine(FILE* fRead,string& line)   //读取一个整数,将整数以转化成字符串用line返回
	{
		int ch = 0;
		while ((ch = getc(fRead)) != EOF&&ch!= '\n')
		{
			line += ch;
		}
		if (ch == EOF)
			return false;
		else
			return true;
	}

	void _InputNum(FILE *fWrite,int num)     //向文件中写一个整数,转化成字符串写入
	{
		char buf[120] = { 0 };
		string line;
		line = _itoa(num, buf, 10);
		line += '\n';
		fputs(line.c_str(), fWrite);
	}

private:
	vector<string> _SmallFileName;     //存放小文件名的数组
	char* _bigfile;               //记录要排序的文件名
	size_t _limit;                //记录小文件的最多存放数据个数
};





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值