10 亿条数据排序:外部排序与 10 路归并完整说明

一、问题背景

在真实生产环境中,10 亿条数据无法一次性加载到内存中排序。即使单条数据只有 8 字节,10 亿条也需要约 8GB 内存,还未考虑 JVM 对象开销、堆外内存、系统占用等问题。

因此,该问题的本质不是"用什么排序算法",而是:

在内存受限的前提下,如何保证最终结果"全局有序"

这类问题在工程上有一个标准解法:外部排序(External Sort)


二、核心结论(先给答案)

10 亿条数据排序的正确做法是:外部排序 = 分块排序 + 多路归并。

  • 分块排序:解决"内存放不下"的问题

  • 多路归并:解决"全局有序"的问题

⚠️ 只做分块排序,不做归并,一定是错误的


三、为什么"分块排序"不等于"整体有序"?

1. 反例说明

假设数据被拆成两个分块:

复制代码
分块 A(已排序):1, 7, 9
分块 B(已排序):4, 5, 6
  • A 内部有序

  • B 内部有序

  • 直接拼接结果:1, 7, 9, 4, 5, 6 ❌(全局无序)

结论:

分块排序只能得到"局部有序",不能保证"全局有序"。


四、真正保证全局有序的关键:归并(Merge)

1. 归并的核心思想

每一步只输出"当前所有未处理数据中的最小值"。

前提条件:

  • 每个分块内部已经有序

  • 分块之间不要求任何数值区间关系


五、两路归并示例(手推理解)

输入

复制代码
A = [1, 7, 9]
B = [4, 5, 6]

归并过程

步骤 A 当前 B 当前 输出 说明
1 1 4 1 1 是当前最小
2 7 4 4 4 < 7
3 7 5 5 5 < 7
4 7 6 6 6 < 7
5 7 - 7,9 B 已空,直接追加

最终结果:

复制代码
1, 4, 5, 6, 7, 9

六、从两路归并到 10 路归并

1. 本质没有变化

  • 两路归并:每次比较 2 个当前元素

  • 10 路归并:每次比较 10 个当前元素

区别仅在于:

比较方式从"if 判断"升级为"小顶堆(PriorityQueue)"。


七、PriorityQueue 是什么?

1. 定义

PriorityQueue 是 Java 提供的 基于堆(Heap)的优先队列实现

它的核心能力是:

可以在 O(log K) 的时间内,插入元素并获取当前最小值。

2. 为什么多路归并一定要用它?

在 10 路归并中:

  • 每一路只暴露一个"当前最小值"

  • 每一步都要从这 10 个值中选出最小的

如果不用堆:

  • 每次都要遍历 10 路(O(K))

  • K 大时性能不可接受


八、10 路归并完整示例

1. 输入(10 个已排序分块)

复制代码
F1:  [ 1, 20, 30 ]
F2:  [ 4, 25, 40 ]
F3:  [ 7, 21, 50 ]
F4:  [ 2, 18, 60 ]
F5:  [ 5, 19, 70 ]
F6:  [ 6, 22, 80 ]
F7:  [ 3, 23, 90 ]
F8:  [ 8, 24,100 ]
F9:  [ 9, 26,110 ]
F10: [10, 27,120 ]

2. 初始堆状态

堆中存放每个分块的第一个元素:

复制代码
1,2,3,4,5,6,7,8,9,10

3. 归并规则

  1. 弹出堆顶(当前全局最小)

  2. 写入结果集

  3. 从该元素所属分块中读取下一个元素入堆

  4. 重复直到所有分块耗尽


九、Java 实现(核心代码)

复制代码
class Node {
    int value;      // 当前值
    int fileIndex;  // 来自第几个分块
    int pos;        // 在该分块中的位置

    Node(int value, int fileIndex, int pos) {
        this.value = value;
        this.fileIndex = fileIndex;
        this.pos = pos;
    }
}

PriorityQueue<Node> heap = new PriorityQueue<>(
        Comparator.comparingInt(n -> n.value)
);

归并循环:

复制代码
while (!heap.isEmpty()) {
    Node cur = heap.poll();
    result.add(cur.value);

    int nextPos = cur.pos + 1;
    if (nextPos < blocks[cur.fileIndex].length) {
        heap.offer(new Node(
                blocks[cur.fileIndex][nextPos],
                cur.fileIndex,
                nextPos
        ));
    }
}

十、复杂度分析(面试必答)

  • 时间复杂度:O(N log K)

    • N:总数据量(10 亿)

    • K:归并路数(10)

  • 空间复杂度:O(K)

    • 堆中最多同时存在 K 个元素

十一、真实生产环境中的外部排序

1. 分块阶段

  • 顺序读取磁盘

  • 控制单块大小(如 500MB ~ 1GB)

  • 内存排序后写回磁盘

2. 归并阶段

  • 使用 BufferedInputStream / FileChannel

  • 每个文件维护一个游标

  • 堆中只保存当前头元素


十二、面试标准总结话术

对于 10 亿条无法一次性加载内存的数据,我会采用外部排序方案:

先进行分块内存排序生成多个有序文件,再通过基于 PriorityQueue 的多路归并,

在 O(N log K) 的复杂度下完成全局有序输出。


十三、关键认知总结

  • 分块排序 ≠ 全局排序

  • 全局有序由"归并"保证

  • PriorityQueue 是多路归并的核心工具

  • 外部排序是工程领域的标准解法


至此,10 亿数据排序问题在逻辑、实现、工程层面已经完全闭环。

相关推荐
__万波__9 小时前
二十三种设计模式(二十)--解释器模式
java·设计模式·解释器模式
网安_秋刀鱼9 小时前
【java安全】反序列化 - CC1链
java·c语言·安全
零度@9 小时前
Java消息中间件-Kafka全解(2026精简版)
java·kafka·c#·linq
钱多多_qdd9 小时前
springboot注解(二)
java·spring boot·后端
Cosmoshhhyyy9 小时前
《Effective Java》解读第32条:谨慎并用泛型和可变参数
java·python
帅气的你10 小时前
面向Java程序员的思维链(CoT)提示词写法学习指南
java
一只小小Java10 小时前
Java面试场景高频题
java·开发语言·面试
沛沛老爹10 小时前
Web开发者快速上手AI Agent:基于Function Calling的12306自动订票系统实战
java·人工智能·agent·web转型
CRUD酱10 小时前
后端使用POI解析.xlsx文件(附源码)
java·后端
亓才孓10 小时前
多态:编译时看左边,运行时看右边
java·开发语言