1. 把 2G 的文件进行分割成大小为 512KB 小文件,总共得到 2048 个小文件,避免一
次性读入整个文件造成内存不足。
2. 定义一个长度为 2048 的 hash 表数组,用来统计每个小文件中单词出现的频率。
3. 使用多线程并行遍历 2048 个小文件,针对每个单词进行 hash 取模运算分别存储
到长度为 2048 的 hash 表数组中
inthash=Math.abs(word.hashCode() %hashTableSize);
hashTables[hash].merge(word, 1, Integer::sum);
4. 接着再遍历这 2048 个 hash 表,把频率前 100 的单词存入小顶堆中
5. 最后,小顶堆中最终得到的 100 个单词,就是 top 100 了。
这种解决方案的核心思想是将大文件分割为多个小文件,然后采用分治和堆的算法,来
解决这个问题。
以下是一个简化的Java实现,来处理大文件,并找出频率最高的前100个单词。请注意,这个实现假设单词之间由空格分隔,并且使用了Java的ConcurrentHashMap
来并行处理哈希表,以及PriorityQueue
来实现小顶堆。
java复制代码
import java.io.*; | |
import java.nio.charset.StandardCharsets; | |
import java.nio.file.*; | |
import java.util.*; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
public class TopWordsFinder { | |
private static final int HASH_TABLE_SIZE = 2048; | |
private static final int TOP_WORDS_COUNT = 100; | |
public static void main(String[] args) throws IOException { | |
String largeFilePath = "path/to/large/file.txt"; // 替换为大文件的路径 | |
String tempDir = "path/to/temp/dir"; // 替换为临时目录的路径 | |
// 1. 分割大文件为512KB的小文件 | |
splitFile(largeFilePath, tempDir, 512 * 1024); | |
// 2. 初始化哈希表数组 | |
ConcurrentHashMap<Integer, Map<String, Integer>> hashTables = new ConcurrentHashMap<>(); | |
for (int i = 0; i < HASH_TABLE_SIZE; i++) { | |
hashTables.put(i, new ConcurrentHashMap<>()); | |
} | |
// 3. 并行遍历小文件,统计单词频率 | |
File[] smallFiles = new File(tempDir).listFiles((dir, name) -> name.endsWith(".txt")); | |
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); | |
for (File smallFile : smallFiles) { | |
executor.submit(() -> { | |
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(smallFile), StandardCharsets.UTF_8))) { | |
String line; | |
while ((line = reader.readLine()) != null) { | |
String[] words = line.split("\\s+"); | |
for (String word : words) { | |
int hash = Math.abs(word.hashCode() % HASH_TABLE_SIZE); | |
hashTables.get(hash).merge(word, 1, Integer::sum); | |
} | |
} | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
}); | |
} | |
executor.shutdown(); | |
while (!executor.isTerminated()) { | |
// 等待所有任务完成 | |
} | |
// 4. 合并哈希表,并把频率前100的单词存入小顶堆 | |
PriorityQueue<Map.Entry<String, Integer>> minHeap = new PriorityQueue<>( | |
(o1, o2) -> o1.getValue().compareTo(o2.getValue()) | |
); | |
hashTables.values().stream().flatMap(Map::entrySet().stream()) | |
.forEach(entry -> minHeap.offer(entry)); | |
// 只保留前TOP_WORDS_COUNT个单词 | |
while (minHeap.size() > TOP_WORDS_COUNT) { | |
minHeap.poll(); | |
} | |
// 5. 输出结果 | |
System.out.println("Top " + TOP_WORDS_COUNT + " words:"); | |
minHeap.stream().forEach(entry -> System.out.println(entry.getKey() + ": " + entry.getValue())); | |
// 清理临时文件 | |
Arrays.stream(smallFiles).forEach(File::delete); | |
} | |
private static void splitFile(String largeFilePath, String tempDir, int size) throws IOException { | |
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(largeFilePath))) { | |
byte[] buffer = new byte[size]; | |
int bytesRead; | |
int fileCount = 0; | |
while ((bytesRead = bis.read(buffer)) != -1) { | |
File smallFile = new File(tempDir, "part-" + fileCount++ + ".txt"); | |
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(smallFile))) { | |
bos.write(buffer, 0, bytesRead); | |
} | |
} | |
} | |
} | |
} |
请注意,这个代码示例假设了所有单词都是有效的UTF-8编码,并且单词之间是由空格分隔的。此外,为了简化代码,错误处理被简化了。在生产环境中,您可能需要添加更多的