目录
3. 读取数据并排序到文件的函数 ReadDataSortToFile
引言
在数据处理的过程中,我们经常会遇到需要对大量数据进行排序的情况。当数据量过大,无法一次性全部加载到内存中时,我们可以采用外部排序的方法,其中文件归并是一种常见的实现方式。本文将详细介绍一段用于文件归并的代码,该代码通过分块排序和逐步归并的方式,最终实现对大量数据的排序。
外排序与内排序的不同
1. 基本概念
- 内排序:内排序是指在排序过程中,所有参与排序的数据都能够一次性加载到计算机的内存(如 RAM)中进行处理。常见的内排序算法有冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序等。这些算法在处理小规模数据时非常高效,因为它们可以直接在内存中对数据进行操作。
- 外排序:外排序则是针对数据量过大,无法一次性全部加载到内存中的情况。在进行外排序时,数据主要存储在外部存储设备(如硬盘、磁带等)上,排序过程需要在内存和外部存储设备之间进行多次数据交换。外排序通常采用分治策略,将大文件分成多个小块,对每个小块进行内排序,然后再将这些有序的小块逐步归并成一个最终的有序文件。归并排序也可以是一种外排序。
代码功能概述
这段代码的主要功能是对一个包含大量整数数据的文件进行排序。具体步骤如下:
- 数据生成:生成一个包含随机整数的文件。
- 分块排序:将大文件分成多个小块,对每个小块进行排序,并将排序结果保存到临时文件中。
- 文件归并:逐步将这些临时文件进行归并,最终得到一个有序的文件。
代码详细分析
1. 数据生成函数 CreateData
void CreateData()
{
// 数据个数
int n = 1000000;
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if(fin == NULL)
{
perror("fopen fin fail!");
exit(1);
}
for(int i = 0; i < n; i++)
{
int x = rand() + i;
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
- 该函数用于生成一个包含 1000 个随机整数的文件
data.txt
。 - 使用
fopen
函数以写入模式打开文件,如果打开失败则输出错误信息并退出程序。 - 通过
rand()
函数生成随机整数,并使用fprintf
函数将整数写入文件。 - 最后使用
fclose
函数关闭文件。
2. 比较函数 compare
(用于qsort函数)
int compare(const void* a, const void* b)
{
return (*(int*)a - *(int*)b);
}
- 该函数是
qsort
函数所需的比较函数,用于比较两个整数的大小。 - 如果
a
小于b
,则返回负数;如果a
等于b
,则返回 0;如果a
大于b
,则返回正数。
3. 读取数据并排序到文件的函数 ReadDataSortToFile
// 返回实际读到的数据个数,若没有则返回 0
int ReadDataSortToFile(FILE* fout, int n, const char* file1)
{
int x = 0;
int* a = (int*)malloc(sizeof(int) * n);
if(a == NULL)
{
perror("malloc fail!");
exit(1);
}
// 想读取 n 个数据,如果遇到文件结束,应该读到 j 个
int j = 0;
for(int i = 0; i < n; i++)
{
if(fscanf(fout, "%d", &x) == EOF)
break;
a[j++] = x;
}
if(j == 0)
{
free(a);
return 0;
}
// 排序
qsort(a, j, sizeof(int), compare);
FILE* fin = fopen(file1, "w");
if(fin == NULL)
{
free(a);
perror("fopen fin fail!");
exit(1);
}
// 写入 file1
for(int i = 0; i < j; i++)
{
fprintf(fin, "%d\n", a[i]);
}
free(a);
fclose(fin);
return j;
}
- 该函数从输入文件
fout
中读取最多n
个整数,并将这些整数存储在动态分配的数组a
中。 - 如果读取的数据个数为 0,则释放数组内存并返回 0。
- 使用
qsort
函数对数组a
进行排序。 - 打开一个新文件
file1
,将排序后的数组元素写入该文件。 - 释放数组内存并关闭文件,最后返回实际读取的数据个数。
4. 文件归并函数 MergeFile
void MergeFile(const char* file1, const char* file2, const char* mfile)
{
FILE* fin1 = fopen(file1, "r");
if(fin1 == NULL)
{
perror("fopen fin1 fail!");
exit(1);
}
FILE* fin2 = fopen(file2, "r");
if(fin2 == NULL)
{
perror("fopen fin2 fail!");
exit(1);
}
FILE* mfin = fopen(mfile, "w");
if(mfin == NULL)
{
perror("fopen mfin fail!");
exit(1);
}
int x1 = 0;
int x2 = 0;
int ret1 = fscanf(fin1, "%d", &x1);
int ret2 = fscanf(fin2, "%d", &x2);
while(ret1 != EOF && ret2 != EOF)
{
if(x1 < x2)
{
fprintf(mfin, "%d\n", x1);
ret1 = fscanf(fin1, "%d", &x1);
}
else
{
fprintf(mfin, "%d\n", x2);
ret2 = fscanf(fin2, "%d", &x2);
}
}
while(ret1 != EOF)
{
fprintf(mfin, "%d\n", x1);
ret1 = fscanf(fin1, "%d", &x1);
}
while(ret2 != EOF)
{
fprintf(mfin, "%d\n", x2);
ret2 = fscanf(fin2, "%d", &x2);
}
fclose(fin1);
fclose(fin2);
fclose(mfin);
}
- 该函数用于将两个已排序的文件
file1
和file2
归并到一个新文件mfile
中。 - 打开三个文件:
file1
和file2
以读取模式打开,mfile
以写入模式打开。 - 从
file1
和file2
中分别读取一个整数,比较它们的大小,将较小的整数写入mfile
中,并继续从相应的文件中读取下一个整数。 - 当其中一个文件读取完毕后,将另一个文件中剩余的整数全部写入
mfile
中。 - 最后关闭所有文件。
5. 主函数 main
int main()
{
srand((unsigned int)time(NULL));
// 造数据
// CreateData();
const char* file1 = "file1.txt";
const char* file2 = "file2.txt";
const char* mfile = "mfile.txt";
FILE* fout = fopen("data.txt", "r");
if(fout == NULL)
{
perror("fopen fout fail!");
exit(1);
}
int m = 100000;
ReadDataSortToFile(fout, m, file1);
ReadDataSortToFile(fout, m, file2);
while(1)
{
MergeFile(file1, file2, mfile);
// 删除 file1 和 file2
remove(file1);
remove(file2);
// 重命名 mfile 为 file1
rename(mfile, file1);
// 当再去读取数据,一个都读不到,说明已经没有数据了
// 已经归并完成,归并结果在 file1
int n = 0;
if((n = ReadDataSortToFile(fout, m, file2)) == 0)
{
break;
}
}
fclose(fout);
return 0;
}
- 初始化随机数种子。
- 打开包含原始数据的文件
data.txt
。 - 调用
ReadDataSortToFile
函数将数据分成两个小块,分别排序并保存到file1.txt
和file2.txt
中。 - 进入循环,不断调用
MergeFile
函数将file1.txt
和file2.txt
归并到mfile.txt
中,然后删除file1.txt
和file2.txt
,将mfile.txt
重命名为file1.txt
。 - 再次调用
ReadDataSortToFile
函数尝试读取数据到file2.txt
中,如果读取的数据个数为 0,则说明所有数据已经归并完成,退出循环。 - 关闭文件并返回 0。
总结
通过分块排序和逐步归并的方式,这段代码实现了对大量数据的排序,避免了一次性将所有数据加载到内存中。这种方法在处理大规模数据时非常有效,可以在有限的内存资源下完成排序任务。