DeepSeek 只用2GB内存在20亿个整数中找到出现次数最多的数 java实现
我来提供一个完整的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亿个整数,通过分治策略将大问题分解为多个可管理的小问题。
更多推荐


所有评论(0)