当1G内存遭遇500G数据文件时,传统排序算法就像试图用汤勺舀干大海。此时外部排序是唯一出路,其核心如同老北京胡同里的分糖葫芦——化整为零、逐个击破 。
核心三步诀:
- 劈文件:将大文件切割成内存可容的小块
- 炼金丹:对每块数据内存排序后存盘
- 合众力:多路归并有序文件得最终结果
代码实战:
分块排序模块
// 文件切割+内存排序
public class ChunkSorter {
private static final int MAX_CHUNK_SIZE = 8_000_000; // 每块存800万ID
void splitAndSort(Path input) throws IOException {
try(BufferedReader reader = Files.newBufferedReader(input)) {
List buffer = new ArrayList<>(MAX_CHUNK_SIZE);
String line;
int chunkIndex = 0;
while ((line = reader.readLine()) != null) {
buffer.add(Long.parseLong(line));
if (buffer.size() >= MAX_CHUNK_SIZE) {
processChunk(buffer, chunkIndex++);
buffer.clear();
}
}
if (!buffer.isEmpty()) processChunk(buffer, chunkIndex);
}
}
private void processChunk(List data, int index) {
Collections.sort(data); // 快速排序炼化数据
writeToTempFile(data, "chunk_"+index+".tmp");
}
}
多路归并模块
// 优先队列实现高效归并
class MergeEngine {
void mergeFiles(List chunks, Path output) throws IOException {
PriorityQueue minHeap = new PriorityQueue<>(
Comparator.comparingLong(FileEntry::currentValue)
);
// 初始化各文件读取器
List readers = chunks.stream()
.map(p -> Files.newBufferedReader(p))
.collect(Collectors.toList());
// 装载首元素建堆
readers.forEach(reader -> {
String firstLine = reader.readLine();
if (firstLine != null) {
minHeap.add(new FileEntry(
Long.parseLong(firstLine), reader
));
}
});
try(BufferedWriter writer = Files.newBufferedWriter(output)) {
while (!minHeap.isEmpty()) {
FileEntry entry = minHeap.poll();
writer.write(entry.currentValue.toString());
writer.newLine();
String nextLine = entry.reader.readLine();
if (nextLine != null) {
minHeap.offer(new FileEntry(
Long.parseLong(nextLine), entry.reader
));
}
}
}
}
}
// 文件条目包装类
record FileEntry(Long currentValue, BufferedReader reader) {}
性能优化:
内存管理
- 缓冲读写:采用BufferedReader/BufferedWriter减少IO次数,就像用大筐搬砖比徒手更高效
- 对象复用:避免在循环内创建临时对象,好比老裁缝节省布料
- 文件删除策略:及时清理临时文件,如同吃完的瓜子壳要扫净
归并策略进阶
当临时文件超过1000个时,采用二阶段归并策略:
- 先将500个文件两两合并为250个
- 重复此过程直至最终合并
踩坑回忆录
去年帮某物流公司处理过类似需求,当时没注意字符编码问题,导致部分ID读取错误。后来用Charset.forName("UTF-8")显式指定编码,才避免数据错乱——这教训就像吃火锅忘关火,汤烧干了才长记性。
效果验证
在AWS c5.xlarge机型实测:
阶段 | 耗时 | 内存峰值 |
分块 | 2.5h | 980MB |
归并 | 1.8h | 820MB |
面对海量数据排序,既要像诸葛亮的谋略——分而治之,又要像张飞的勇猛——直面性能瓶颈。记住:没有解决不了的问题,只有没找对的方法。下次遇到数据难题时,不妨泡壶铁观音,让分治算法在茶香中起舞吧!
本文暂时没有评论,来添加一个吧(●'◡'●)