文件中有1亿个数据需要排序,假设内存最多存1000W个数据,怎么办?

首先来认识两个函数先:

fscanf

fscanf 是 C 语言标准库 <stdio.h> 中的一个输入函数,用于从指定的文件流中按格式读取数据。它的功能与 scanf 类似,区别在于 scanf 从标准输入流(stdin,通常是键盘)读取数据,而 fscanf 可以从任意文件流(如已打开的文件)读取。

int fscanf(FILE* stream, const char* format, ...);

参数:

  stream:文件指针,指向要读取数据的文件流(如通过 fopen 打开的文件)。

  format:格式控制字符串,指定读取数据的类型和格式(与 scanf 的格式字符串规则完全一致)。

  ...:可变参数列表,存放接收数据的变量地址(需用 & 取地址,除字符串数组外)。

返回值:

  成功读取的数据项数量(匹配并赋值的变量个数)。

  若读取失败(如遇到文件末尾或格式不匹配),返回 EOF(通常定义为 -1)。

注意事项:

  1. 文件流必须先打开:使用 fscanf 前,需通过 fopen 打开文件并获得有效的 FILE* 指针,否则会导致未定义行为。

  2. 格式匹配问题:若输入数据与格式字符串不匹配(如用 %d 读取字母),fscanf 会停止读取,返回已成功匹配的数量。

  3. 字符串读取风险:%s 读取字符串时不会检查目标数组的长度,可能导致缓冲区溢出,建议使用 %ns(n 为最大长度)限制,如 %10s 最多读取 10 个字符(含终止符 \0)。

  4. 文件末尾判断:当读取到文件末尾时,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);
}

更多推荐