【数据结构】1亿数据排序:内存不足的解决之道
文件中有1亿个数据需要排序,假设内存最多存1000W个数据,怎么办?
首先来认识两个函数先:
fscanf
fscanf 是 C 语言标准库 <stdio.h> 中的一个输入函数,用于从指定的文件流中按格式读取数据。它的功能与 scanf 类似,区别在于 scanf 从标准输入流(stdin,通常是键盘)读取数据,而 fscanf 可以从任意文件流(如已打开的文件)读取。
int fscanf(FILE* stream, const char* format, ...);
参数:
stream:文件指针,指向要读取数据的文件流(如通过 fopen 打开的文件)。
format:格式控制字符串,指定读取数据的类型和格式(与 scanf 的格式字符串规则完全一致)。
...:可变参数列表,存放接收数据的变量地址(需用 & 取地址,除字符串数组外)。
返回值:
成功读取的数据项数量(匹配并赋值的变量个数)。
若读取失败(如遇到文件末尾或格式不匹配),返回 EOF(通常定义为 -1)。
注意事项:
-
文件流必须先打开:使用
fscanf前,需通过fopen打开文件并获得有效的FILE*指针,否则会导致未定义行为。 -
格式匹配问题:若输入数据与格式字符串不匹配(如用
%d读取字母),fscanf会停止读取,返回已成功匹配的数量。 -
字符串读取风险:
%s读取字符串时不会检查目标数组的长度,可能导致缓冲区溢出,建议使用%ns(n为最大长度)限制,如%10s最多读取 10 个字符(含终止符\0)。 -
文件末尾判断:当读取到文件末尾时,
fscanf返回EOF,可用于循环读取文件内容:int num; while (fscanf(fp, "%d", &num) == 1) { // 循环读取整数,直到文件结束 printf("%d ", num); }fprintf
fprintf是 C 语言标准库<stdio.h>中的输出函数,用于将格式化的数据写入指定的文件流(如文件、标准输出等)。它的功能与printf类似,区别在于printf固定输出到标准输出流(stdin,通常是屏幕),而fprintf可以指定输出目标。
int fprintf(FILE* stream, const char* format, ...);
参数:
stream:文件指针,指向数据要写入的目标流(如通过 fopen 打开的文件、stdout 标准输出等)。
format:格式控制字符串,指定输出的格式(与 printf 规则完全一致)。
...:可变参数列表,存放要输出的数据(如整数、字符串等)。
返回值:
成功时,返回写入的字符总数(不包括字符串终止符 \0)。
失败时,返回负数(通常为 -1)。
我们这里先再工程的运行目录下加一个txt文件放我们待排序的数据,同时创一个sub(可以通过代码自定义)的文件放排好序的n个文件

如果数据是换行排列的,读取方式是while (fscanf(fout, "%d\n", &num) != EOF),如果是 空格隔开是while (fscanf(fout, "%d ", &num) != EOF),要根据实际情况而定

我们可以将1亿个数据读出来,切分成10份,保证每份的大小可以加载到内存;

文件的数据分割成一段一段数据,内存排序后写到小文件,第一个文件的数据和第二个文件的数据排好后进行归并,第三个文件再和这个合并出来的文件进行归并
归并排序:
QuickSort的实现:【数据结构】快速排序算法精髓解析-CSDN博客
void MergeSortFile(const char* file)
{
FILE* fout = fopen(file, "r");
if (fout == NULL)
{
printf("打开文件失败\n");
exit(-1);
}
//分割成一段一段数据,内存排序后写到小文件
int n = 10;
int a[10];
int i = 0;
int num = 0;
char subfile[20];
int filei = 0;
memset(a, 0, sizeof(int) * n);
while (fscanf(fout, "%d\n", &num) != EOF)
{
if (i < n-1)
{
a[i++] = num;
}
else
{
a[i] = num;
QuickSort(a, 0, n - 1);
sprintf(subfile, "sub\\sub_sort%d.txt", filei++);
FILE* fin = fopen(subfile, "w");
if (fin == NULL) // 检查子文件是否打开成功
{
perror("创建子文件失败(可能sub目录不存在)");
fclose(fout);
exit(-1);
}
for (int i = 0; i < n; i++)
{
fprintf(fin,"%d\n", a[i]);
}
fclose(fin);
i = 0;
memset(a, 0, sizeof(int)*n);
}
}
fclose(fout);
}
main函数调用MergeSortFile("sort.txt");
这10个文件的数据都是有序的


最后把他们合并到一个文件进行输出,以下是最终优化代码:
void _MergeFile(const char* file1, const char* file2, const char* mfile)
{
FILE* fout1 = fopen(file1, "r");
if (fout1 == NULL)
{
printf("打开文件失败\n");
exit(-1);
}
FILE* fout2 = fopen(file2, "r");
if (fout2 == NULL)
{
printf("打开文件失败\n");
exit(-1);
}
FILE* fin = fopen(mfile, "w");
if (fin == NULL) // 检查子文件是否打开成功
{
perror("创建子文件失败(可能sub目录不存在)");
fclose(fin);
exit(-1);
}
int num1, num2;
// 第一次读取(指针自动移动到下一个位置)
int ret1 = fscanf(fout1, "%d\n", &num1);
int ret2 = fscanf(fout2, "%d\n", &num2);
//类似 “双指针合并数组”,每次取两个文件中当前较小的数写入合并文件,
// 同时推进对应文件的读取指针。
//文件指针会自动推进,但 fscanf 是 “一次性读取” 操作
// —— 每次调用只读取当前位置的数据,不会主动循环读取。
while (ret1!=EOF&&ret2!=EOF)
{
if (num1 < num2)
{
fprintf(fin, "%d\n", num1);
// 再次调用时,从指针当前位置继续读取(无需手动调整指针)
ret1 = fscanf(fout1, "%d\n", &num1);
}
else
{
fprintf(fin, "%d\n", num2);
ret2 = fscanf(fout2, "%d\n", &num2);
}
}
//当其中一个文件读完后,将另一个文件的剩余数据全部写入合并文件;
while (ret1 != EOF)
{
fprintf(fin, "%d\n", num1);
ret1 = fscanf(fout1, "%d\n", &num1);
}
while (ret2 != EOF)
{
fprintf(fin, "%d\n", num2);
ret2 = fscanf(fout2, "%d\n", &num2);
}
fclose(fout1);
fclose(fout2);
fclose(fin);
}
// 外部归并排序主函数
void MergeSortFile(const char* inputFile, const char* outputFile) {
FILE* fin = fopen(inputFile, "r");
if (fin == NULL) {
perror("打开输入文件失败");
exit(EXIT_FAILURE);
}
int buffer[10];
int count = 0;
int fileIndex = 1;
char subFileName[20];
// 读取并分割文件
int num;
while (fscanf(fin, "%d\n", &num) != EOF) {
buffer[count++] = num;
// 当缓冲区满时,排序并写入子文件
if (count == 10) {
QuickSort(buffer, 0, 10 - 1);
sprintf(subFileName, "sub_%d", fileIndex++);
FILE* fout = fopen(subFileName, "w");
if (fout == NULL) {
perror("创建子文件失败");
fclose(fin);
exit(EXIT_FAILURE);
}
for (int i = 0; i < 10; i++) {
fprintf(fout, "%d\n", buffer[i]);
}
fclose(fout);
count = 0; // 重置缓冲区计数
}
}
// 处理最后不足一块的数据
if (count > 0) {
QuickSort(buffer, 0, count - 1);
sprintf(subFileName, "sub_%d", fileIndex++);
FILE* fout = fopen(subFileName, "w");
if (fout == NULL) {
perror("创建子文件失败");
fclose(fin);
exit(EXIT_FAILURE);
}
for (int i = 0; i < count; i++) {
fprintf(fout, "%d\n", buffer[i]);
}
fclose(fout);
}
fclose(fin);
// 合并所有子文件
int totalSubFiles = fileIndex - 1;
if (totalSubFiles == 0) {
printf("没有数据需要排序\n");
return;
}
// 如果只有一个子文件,直接重命名为结果文件
if (totalSubFiles == 1) {
char src[20];
sprintf(src, "sub_%d", 1);
rename(src, outputFile);
return;
}
// 初始合并前两个子文件
char file1[20], file2[20], mergedFile[20];
sprintf(file1, "sub_%d", 1);
sprintf(file2, "sub_%d", 2);
sprintf(mergedFile, "merge_%d", 1);
_MergeFile(file1, file2, mergedFile);
// 删除已合并的子文件
remove(file1);
remove(file2);
// 合并剩余的子文件
for (int i = 3; i <= totalSubFiles; i++) {
char newMergedFile[20];
sprintf(file2, "sub_%d", i);
sprintf(newMergedFile, "merge_%d", i - 1);
_MergeFile(mergedFile, file2, newMergedFile);
// 删除已合并的文件
remove(mergedFile);
remove(file2);
// 更新合并文件名
strcpy(mergedFile, newMergedFile);
}
// 将最终合并结果重命名为输出文件
rename(mergedFile, outputFile);
printf("排序完成,结果已保存到 %s\n", outputFile);
}
更多推荐
所有评论(0)