我来提供一个完整的Java实现,使用分治法在2GB内存限制下处理20亿个整数:

核心实现

```java
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

public class MostFrequentNumberFinder {
    
    private static final int PARTITIONS = 100; // 分区数量,可调整
    private static final int BUFFER_SIZE = 8 * 1024 * 1024; // 8MB缓冲区
    private static final int MAX_MEMORY_MB = 1800; // 最大内存使用限制(留200MB缓冲)
    
    // 主方法
    public static void main(String[] args) throws Exception {
        String inputFile = "large_data.bin"; // 二进制输入文件
        String tempDir = "temp_data";
        
        long startTime = System.currentTimeMillis();
        
        // 1. 分割阶段
        System.out.println("Phase 1: Partitioning data...");
        partitionData(inputFile, tempDir);
        
        // 2. 统计阶段
        System.out.println("Phase 2: Counting frequencies...");
        MostFrequentResult result = countFrequencies(tempDir);
        
        // 3. 清理临时文件
        System.out.println("Phase 3: Cleaning up...");
        cleanup(tempDir);
        
        long endTime = System.currentTimeMillis();
        
        System.out.println("\n=== Result ===");
        System.out.println("Most frequent number: " + result.number);
        System.out.println("Frequency: " + result.frequency);
        System.out.println("Time elapsed: " + (endTime - startTime) + "ms");
    }
    
    // 第一阶段:分割数据
    private static void partitionData(String inputFile, String tempDir) throws IOException {
        Files.createDirectories(Paths.get(tempDir));
        
        // 创建分区文件
        DataOutputStream[] partitionWriters = new DataOutputStream[PARTITIONS];
        for (int i = 0; i < PARTITIONS; i++) {
            partitionWriters[i] = new DataOutputStream(
                new BufferedOutputStream(
                    new FileOutputStream(tempDir + "/part_" + i + ".dat"),
                    BUFFER_SIZE
                )
            );
        }
        
        try (FileChannel channel = FileChannel.open(Paths.get(inputFile), StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
            long fileSize = channel.size();
            long bytesRead = 0;
            
            while (bytesRead < fileSize) {
                buffer.clear();
                channel.read(buffer);
                buffer.flip();
                
                while (buffer.remaining() >= 4) { // 每个int 4字节
                    int number = buffer.getInt();
                    int partition = Math.abs(number) % PARTITIONS;
                    partitionWriters[partition].writeInt(number);
                }
                
                bytesRead += buffer.position();
                System.out.printf("Progress: %.2f%%\r", (bytesRead * 100.0 / fileSize));
            }
        } finally {
            for (DataOutputStream writer : partitionWriters) {
                if (writer != null) writer.close();
            }
        }
    }
    
    // 第二阶段:统计频率
    private static MostFrequentResult countFrequencies(String tempDir) throws Exception {
        List<Future<MostFrequentResult>> futures = new ArrayList<>();
        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        
        // 并行处理每个分区
        for (int i = 0; i < PARTITIONS; i++) {
            final int partitionId = i;
            futures.add(executor.submit(() -> processPartition(tempDir, partitionId)));
        }
        
        // 合并结果
        MostFrequentResult finalResult = new MostFrequentResult(0, 0);
        for (Future<MostFrequentResult> future : futures) {
            MostFrequentResult partitionResult = future.get();
            if (partitionResult.frequency > finalResult.frequency) {
                finalResult = partitionResult;
            }
        }
        
        executor.shutdown();
        return finalResult;
    }
    
    // 处理单个分区
    private static MostFrequentResult processPartition(String tempDir, int partitionId) throws IOException {
        String partitionFile = tempDir + "/part_" + partitionId + ".dat";
        
        // 使用HashMap统计频率(内存限制下安全)
        HashMap<Integer, Long> frequencyMap = new HashMap<>(1_000_000); // 预分配容量
        
        try (DataInputStream dis = new DataInputStream(
                new BufferedInputStream(
                    new FileInputStream(partitionFile),
                    BUFFER_SIZE
                ))) {
            
            while (dis.available() > 0) {
                int number = dis.readInt();
                frequencyMap.put(number, frequencyMap.getOrDefault(number, 0L) + 1);
                
                // 监控内存使用(可选)
                if (frequencyMap.size() % 1_000_000 == 0) {
                    Runtime runtime = Runtime.getRuntime();
                    long usedMB = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
                    if (usedMB > MAX_MEMORY_MB) {
                        System.out.println("Warning: Memory usage high in partition " + partitionId);
                    }
                }
            }
        }
        
        // 找出分区中出现次数最多的数
        int maxNumber = 0;
        long maxFrequency = 0;
        for (Map.Entry<Integer, Long> entry : frequencyMap.entrySet()) {
            if (entry.getValue() > maxFrequency) {
                maxFrequency = entry.getValue();
                maxNumber = entry.getKey();
            }
        }
        
        // 保存中间结果(可选,用于调试)
        saveFrequencyMap(frequencyMap, tempDir + "/freq_" + partitionId + ".txt");
        
        return new MostFrequentResult(maxNumber, maxFrequency);
    }
    
    // 保存频率映射到文件(用于调试或验证)
    private static void saveFrequencyMap(Map<Integer, Long> map, String filename) throws IOException {
        try (PrintWriter writer = new PrintWriter(filename)) {
            for (Map.Entry<Integer, Long> entry : map.entrySet()) {
                writer.println(entry.getKey() + ":" + entry.getValue());
            }
        }
    }
    
    // 清理临时文件
    private static void cleanup(String tempDir) throws IOException {
        Path dir = Paths.get(tempDir);
        Files.walk(dir)
            .sorted(Comparator.reverseOrder())
            .map(Path::toFile)
            .forEach(File::delete);
    }
    
    // 结果类
    static class MostFrequentResult {
        int number;
        long frequency;
        
        MostFrequentResult(int number, long frequency) {
            this.number = number;
            this.frequency = frequency;
        }
    }
    
    // 辅助方法:生成测试数据
    public static void generateTestData(String filename, long count) throws IOException {
        Random random = new Random();
        try (DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(
                    new FileOutputStream(filename),
                    BUFFER_SIZE
                ))) {
            for (long i = 0; i < count; i++) {
                // 生成测试数据:大部分是随机数,但让某些数字重复出现
                int num;
                if (i % 100 == 0) {
                    num = 999999; // 高频数字
                } else if (i % 500 == 0) {
                    num = 888888; // 中频数字
                } else {
                    num = random.nextInt(1_000_000);
                }
                dos.writeInt(num);
            }
        }
        System.out.println("Generated " + count + " integers in " + filename);
    }
}
```

优化的内存友好版本(使用内存映射文件)

```java
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class OptimizedMostFrequentFinder {
    
    private static final int PARTITIONS = 100;
    private static final int CHUNK_SIZE = 64 * 1024 * 1024; // 64MB块大小
    
    public static void main(String[] args) throws Exception {
        String inputFile = "large_data.bin";
        String tempDir = "temp_partitions";
        
        long start = System.currentTimeMillis();
        
        // 使用内存映射文件提高IO性能
        partitionUsingMemoryMap(inputFile, tempDir);
        
        MostFrequentResult result = parallelProcessPartitions(tempDir);
        
        System.out.println("Result: Number=" + result.number + 
                          ", Frequency=" + result.frequency);
        System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");
        
        // 清理
        cleanup(tempDir);
    }
    
    private static void partitionUsingMemoryMap(String inputFile, String tempDir) throws IOException {
        Files.createDirectories(Paths.get(tempDir));
        
        // 创建分区文件
        FileChannel[] channels = new FileChannel[PARTITIONS];
        for (int i = 0; i < PARTITIONS; i++) {
            Path path = Paths.get(tempDir, "part_" + i + ".dat");
            channels[i] = FileChannel.open(path, 
                StandardOpenOption.CREATE, 
                StandardOpenOption.WRITE, 
                StandardOpenOption.READ);
        }
        
        try (FileChannel inputChannel = FileChannel.open(Paths.get(inputFile), StandardOpenOption.READ)) {
            long fileSize = inputChannel.size();
            long position = 0;
            
            while (position < fileSize) {
                long remaining = fileSize - position;
                long chunkSize = Math.min(CHUNK_SIZE, remaining);
                
                MappedByteBuffer buffer = inputChannel.map(
                    FileChannel.MapMode.READ_ONLY, 
                    position, 
                    chunkSize
                );
                
                // 确保读取完整整数
                int intCount = (int) (chunkSize / 4);
                for (int i = 0; i < intCount; i++) {
                    int number = buffer.getInt();
                    int partition = Math.abs(number) % PARTITIONS;
                    
                    // 写入分区文件
                    ByteBuffer singleInt = ByteBuffer.allocate(4);
                    singleInt.putInt(number);
                    singleInt.flip();
                    channels[partition].write(singleInt);
                }
                
                position += chunkSize;
                System.out.printf("Partition progress: %.2f%%\r", (position * 100.0 / fileSize));
            }
        } finally {
            for (FileChannel channel : channels) {
                if (channel != null) channel.close();
            }
        }
    }
    
    private static MostFrequentResult parallelProcessPartitions(String tempDir) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(
            Runtime.getRuntime().availableProcessors()
        );
        
        List<Future<MostFrequentResult>> futures = new ArrayList<>();
        AtomicInteger processed = new AtomicInteger(0);
        
        for (int i = 0; i < PARTITIONS; i++) {
            final int partitionId = i;
            futures.add(executor.submit(() -> {
                MostFrequentResult result = processPartitionMemoryEfficient(
                    tempDir + "/part_" + partitionId + ".dat"
                );
                System.out.printf("Partition %d processed. Progress: %d/%d\n",
                    partitionId, processed.incrementAndGet(), PARTITIONS);
                return result;
            }));
        }
        
        MostFrequentResult finalResult = new MostFrequentResult(0, 0);
        for (Future<MostFrequentResult> future : futures) {
            MostFrequentResult partitionResult = future.get();
            if (partitionResult.frequency > finalResult.frequency) {
                finalResult = partitionResult;
            }
        }
        
        executor.shutdown();
        return finalResult;
    }
    
    private static MostFrequentResult processPartitionMemoryEfficient(String filePath) throws IOException {
        // 使用布隆过滤器预筛选(可选优化)
        Map<Integer, Long> frequencyMap = new HashMap<>();
        
        try (FileInputStream fis = new FileInputStream(filePath);
             BufferedInputStream bis = new BufferedInputStream(fis, 8192);
             DataInputStream dis = new DataInputStream(bis)) {
            
            // 使用固定大小缓冲区读取
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = bis.read(buffer)) > 0) {
                int intCount = bytesRead / 4;
                ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, bytesRead);
                DataInputStream sliceDis = new DataInputStream(bais);
                
                for (int i = 0; i < intCount; i++) {
                    int num = sliceDis.readInt();
                    frequencyMap.merge(num, 1L, Long::sum);
                }
            }
        }
        
        // 找出最大值
        int maxNum = 0;
        long maxFreq = 0;
        for (Map.Entry<Integer, Long> entry : frequencyMap.entrySet()) {
            if (entry.getValue() > maxFreq) {
                maxFreq = entry.getValue();
                maxNum = entry.getKey();
            }
        }
        
        return new MostFrequentResult(maxNum, maxFreq);
    }
    
    private static void cleanup(String tempDir) throws IOException {
        Path dir = Paths.get(tempDir);
        if (Files.exists(dir)) {
            Files.walk(dir)
                .sorted(Comparator.reverseOrder())
                .map(Path::toFile)
                .forEach(File::delete);
        }
    }
    
    static class MostFrequentResult {
        int number;
        long frequency;
        
        MostFrequentResult(int number, long frequency) {
            this.number = number;
            this.frequency = frequency;
        }
    }
}
```

简化版本(适用于较小规模数据)

```java
import java.util.*;
import java.io.*;

public class SimpleMostFrequentFinder {
    
    public static void main(String[] args) throws IOException {
        String inputFile = "data.txt"; // 每行一个整数
        Map<Integer, Long> frequencyMap = new HashMap<>();
        
        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
            String line;
            int lineCount = 0;
            
            while ((line = reader.readLine()) != null) {
                int num = Integer.parseInt(line.trim());
                frequencyMap.put(num, frequencyMap.getOrDefault(num, 0L) + 1);
                
                // 监控内存使用
                if (++lineCount % 1_000_000 == 0) {
                    System.out.println("Processed " + lineCount + " lines");
                    Runtime runtime = Runtime.getRuntime();
                    long usedMB = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
                    if (usedMB > 1800) {
                        System.out.println("Warning: Memory usage high");
                        // 可以在这里保存部分结果并清理map
                    }
                }
            }
        }
        
        // 找出出现次数最多的数
        int mostFrequent = 0;
        long maxCount = 0;
        
        for (Map.Entry<Integer, Long> entry : frequencyMap.entrySet()) {
            if (entry.getValue() > maxCount) {
                maxCount = entry.getValue();
                mostFrequent = entry.getKey();
            }
        }
        
        System.out.println("Most frequent number: " + mostFrequent);
        System.out.println("Frequency: " + maxCount);
    }
}
```

测试数据生成器

```java
import java.io.*;
import java.util.Random;

public class DataGenerator {
    
    public static void main(String[] args) throws IOException {
        generateBinaryData("large_data.bin", 2_000_000_000L);
    }
    
    public static void generateBinaryData(String filename, long count) throws IOException {
        Random random = new Random(42); // 固定种子以便重现
        
        try (DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(
                    new FileOutputStream(filename),
                    8192
                ))) {
            
            // 生成一些高频数字
            int frequentNumber1 = 123456;
            int frequentNumber2 = 789012;
            
            for (long i = 0; i < count; i++) {
                int num;
                
                // 控制一些数字的出现频率
                if (i % 1000 == 0) {
                    num = frequentNumber1; // 每1000个中出现1次
                } else if (i % 5000 == 0) {
                    num = frequentNumber2; // 每5000个中出现1次
                } else {
                    // 其他随机数字
                    num = random.nextInt(1_000_000);
                }
                
                dos.writeInt(num);
                
                // 显示进度
                if (i % 100_000_000 == 0) {
                    System.out.printf("Generated: %.2f%%\n", (i * 100.0 / count));
                }
            }
        }
        
        System.out.println("Generated " + count + " integers in " + filename);
    }
}
```

使用说明

1. 生成测试数据:
   ```java
   DataGenerator.generateBinaryData("test.bin", 1_000_000); // 生成100万个整数测试
   ```
2. 运行主程序:
   ```bash
   # 编译
   javac MostFrequentNumberFinder.java
   
   # 运行(设置堆内存)
   java -Xmx2g -Xms2g MostFrequentNumberFinder
   ```
3. 调整参数:
   · PARTITIONS: 分区数量,根据可用内存调整
   · BUFFER_SIZE: 缓冲区大小,影响IO性能
   · 使用更多线程可以加速处理

关键优化点

1. 内存映射文件:提高大文件读取性能
2. 分批处理:避免一次性加载全部数据
3. 并行处理:充分利用多核CPU
4. 流式处理:边读边处理,不保存中间数据
5. 内存监控:防止超出2GB限制

这个实现可以在2GB内存限制下处理20亿个整数,通过分治策略将大问题分解为多个可管理的小问题。

 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐