场景题:100G的文件里有很多id,用1G内存的机器排序,怎么做?

海量数据排序思路

核心方案:外排序(分治+多路归并)MapReduce

外排序是指数据量太大,无法全部加载到内存中,需要将数据分成多个小块进行排序,然后将排序后的小块合并成一个大的有序块

1.分块排序(Map阶段)

  • 分块策略 按1G内存容量限制,将100G文件拆分为 200个500MB分块(保留内存用于排序计算和系统开销)
  • 内存排序 每个分块加载至内存后: ① 使用快速排序(时间复杂度O(n log n)) ② 去重优化:若存在重复ID,排序时合并相同项(节省存储空间),根据要求是否去重
  • 临时文件写入 排序后分块写入磁盘,命名规则:chunk_0001.sorted ~ chunk_0200.sorted

2. 多路归并(Reduce阶段)

使用K路归并算法合并这些排序好的小文件

具体实现方法:

  • 从每个小文件中读取一小部分数据到内存(例如每个文件读取几MB)
  • 建立最小堆(优先队列)来追踪当前每个文件的最小值
  • 每次从堆中取出最小值,写入输出文件
  • 从该最小值所在的文件再读入一个ID到堆中
  • 重复上述过程直到所有文件都处理完

具体示例:

  • 分批归并:每轮合并50个分块(50路归并),共需4轮(200/50)
  • 堆排序优化 :使用 最小堆(PriorityQueue) 管理各分块当前读取值

内存管理:

区域 分配比例 说明
输入缓冲区 800MB 每个分块预读16MB(50分块×16MB)
输出缓冲区 200MB 写满后批量刷新至磁盘

3.核心代码(Java实现)

阶段1:分割大文件,并排序小文件,输出多个小文件排序后的结果

java 复制代码
private static List<File> splitAndSort(String inputFile) throws IOException {
        List<File> chunks = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
            List<Long> buffer = new ArrayList<>(MAX_CHUNK_SIZE / 8);
            String line;
            
            while ((line = reader.readLine()) != null) {
                buffer.add(Long.parseLong(line));
                // 内存达到阈值时执行排序和写入
                if (buffer.size() >= MAX_CHUNK_SIZE / 8) {
                    chunks.add(sortAndWrite(buffer));
                    buffer.clear();
                }
            }
            // 处理剩余数据
            if (!buffer.isEmpty()) {
                chunks.add(sortAndWrite(buffer));
            }
        }
        return chunks;
    }

阶段2:多路归并,使用最小堆优化

java 复制代码
 private static void mergeFiles(List<File> files, String output) throws IOException {
        PriorityQueue<FileEntry> minHeap = new PriorityQueue<>(
            files.size(), 
            Comparator.comparingLong(FileEntry::current)
        );

        // 初始化堆(每个文件读取一个元素)
        for (File file : files) {
            BufferedReader reader = new BufferedReader(new FileReader(file));
            String line = reader.readLine();
            if (line != null) {
                minHeap.offer(new FileEntry(Long.parseLong(line), reader));
            }
        }

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(output))) {
            while (!minHeap.isEmpty()) {
                FileEntry entry = minHeap.poll();
                writer.write(entry.current.toString());
                writer.newLine();
                
                // 从当前文件读取下一个元素
                String line = entry.reader.readLine();
                if (line != null) {
                    entry.current = Long.parseLong(line);
                    minHeap.offer(entry);
                } else {
                    entry.reader.close(); // 关闭已读完的文件
                }
            }
        }
    }
相关推荐
IT_陈寒27 分钟前
Redis缓存击穿:3个鲜为人知的防御策略,90%开发者都忽略了!
前端·人工智能·后端
uzong1 小时前
Harness Engineering 是什么?一场新的 AI 范式已经开始
人工智能·后端·架构
左左右右左右摇晃2 小时前
Java并发——synchronized锁
java·开发语言
唐叔在学习2 小时前
Python桌面端应用最小化托盘开发实践
后端·python·程序员
消失的旧时光-19432 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
yuhaiqiang2 小时前
被 AI 忽悠后,开始怀念搜索引擎了?
前端·后端·面试
li星野2 小时前
[特殊字符] Linux/嵌入式Linux面试模拟卷
linux·运维·面试
sxlishaobin2 小时前
Java I/O 模型详解:BIO、NIO、AIO
java·开发语言·nio
二闹2 小时前
Python文件读取三巨头你该选择哪一个?
后端·python
彭于晏Yan2 小时前
Spring AI(二):入门使用
java·spring boot·spring·ai