当需要处理的数据量超过内存可用空间时,直接调用标准库的排序函数无法完成排序任务,此时需要采用内存分块排序的思路,也就是外部排序的核心实现方案。该方案的核心逻辑是将大文件拆分成多个能放入内存的小块,分别排序后通过多路归并的方式得到最终有序的大文件。

核心实现步骤
1. 数据分块与块内排序
首先读取原始大文件,每次读取固定大小的数据到内存中,对该块数据使用快速排序等高效算法完成排序后,写入到临时文件中,重复这个过程直到原始文件全部处理完成。每个临时文件内部都是有序的,后续只需要对这些有序块做归并即可。
下面是分块排序的实现代码:
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <string>
// 块大小,可根据实际内存调整,单位字节
const int BLOCK_SIZE = 1024 * 1024 * 10; // 10MB
// 对单个块进行排序并写入临时文件
void sort_block(const std::vector<int>& block, int block_index, std::vector<std::string>& temp_files) {
std::vector<int> sorted_block = block;
std::sort(sorted_block.begin(), sorted_block.end());
std::string temp_file = "temp_" + std::to_string(block_index) + ".txt";
std::ofstream ofs(temp_file, std::ios::binary);
ofs.write(reinterpret_cast<const char*>(sorted_block.data()), sorted_block.size() * sizeof(int));
ofs.close();
temp_files.push_back(temp_file);
}
// 分块读取并排序原始文件
void split_and_sort(const std::string& input_file, std::vector<std::string>& temp_files) {
std::ifstream ifs(input_file, std::ios::binary);
std::vector<int> block;
block.reserve(BLOCK_SIZE / sizeof(int));
int block_index = 0;
int num;
while (ifs.read(reinterpret_cast<char*>(&num), sizeof(int))) {
block.push_back(num);
// 块大小达到阈值时排序写入
if (block.size() * sizeof(int) >= BLOCK_SIZE) {
sort_block(block, block_index++, temp_files);
block.clear();
}
}
// 处理最后不足一个块大小的数据
if (!block.empty()) {
sort_block(block, block_index, temp_files);
}
ifs.close();
}
2. 多路归并有序块
所有临时有序块生成后,需要同时打开所有临时文件,每次从每个文件的当前位置读取一个元素,选出最小的元素写入最终输出文件,然后移动对应文件的读取位置,重复这个过程直到所有临时文件都被读取完毕。为了减少IO次数,可以给每个文件维护一个缓冲区,每次读取一批数据到内存中。
多路归并的实现代码如下:
#include <queue>
#include <memory>
struct Element {
int value;
int file_index;
Element(int v, int idx) : value(v), file_index(idx) {}
// 优先队列默认大顶堆,自定义比较规则实现小顶堆
bool operator<(const Element& other) const {
return value > other.value;
}
};
// 多路归并临时文件到输出文件
void merge_blocks(const std::vector<std::string>& temp_files, const std::string& output_file) {
int file_count = temp_files.size();
std::vector<std::ifstream> ifs_list;
std::vector<int> buffers(file_count);
std::vector<bool> has_data(file_count, false);
std::priority_queue<Element> min_heap;
// 打开所有临时文件,读取每个文件的第一个元素
for (int i = 0; i < file_count; ++i) {
ifs_list.emplace_back(temp_files[i], std::ios::binary);
if (ifs_list[i].read(reinterpret_cast<char*>(&buffers[i]), sizeof(int))) {
has_data[i] = true;
min_heap.emplace(buffers[i], i);
}
}
std::ofstream ofs(output_file, std::ios::binary);
while (!min_heap.empty()) {
Element cur = min_heap.top();
min_heap.pop();
ofs.write(reinterpret_cast<const char*>(&cur.value), sizeof(int));
int file_idx = cur.file_index;
// 读取对应文件的下一个元素
if (ifs_list[file_idx].read(reinterpret_cast<char*>(&buffers[file_idx]), sizeof(int))) {
min_heap.emplace(buffers[file_idx], file_idx);
}
}
ofs.close();
// 关闭所有临时文件并删除
for (int i = 0; i < file_count; ++i) {
ifs_list[i].close();
remove(temp_files[i].c_str());
}
}
3. 主流程串联
将分块排序和归并的流程组合起来,就是完整的内存分块排序实现,主函数代码如下:
int main() {
std::string input_file = "large_data.bin";
std::string output_file = "sorted_data.bin";
std::vector<std::string> temp_files;
// 步骤1:分块排序生成临时有序文件
split_and_sort(input_file, temp_files);
// 步骤2:多路归并临时文件得到最终有序文件
merge_blocks(temp_files, output_file);
std::cout << "排序完成,结果文件:" << output_file << std::endl;
return 0;
}
性能优化要点
- 块大小设置:块大小需要根据实际可用内存调整,尽量让块大小接近内存上限,减少临时文件的数量,降低归并时的IO开销。
- 缓冲区优化:归并阶段给每个临时文件设置独立的读缓冲区,每次读取多个元素到内存,减少文件读取次数。
- 排序算法选择:块内排序优先选择快速排序、内省排序等时间复杂度稳定为O(n log n)的算法,C++标准库的
std::sort就是内省排序实现,性能表现优秀。 - 归并路数控制:如果临时文件数量过多,可以分多轮做归并,避免同时打开过多文件导致系统资源不足。
注意事项
实际使用时需要根据数据类型调整代码中的int类型,如果是其他类型的数据,需要修改对应的读写逻辑和排序比较规则。如果原始文件不是二进制格式,需要调整为文本读取的方式,按行或者按分隔符解析数据后再做排序。临时文件的路径需要确保有写入权限,避免运行过程中出现文件操作失败的问题。