Java如何基于ThreadPoolExecutor实现多线程递归搜索目录及子目录下txt/html文件指定词汇
实现思路
你的场景属于IO密集型任务,用ThreadPoolExecutor改造非常合适,这里我们选择Runnable实现单文件扫描任务即可,满足需求的同时代码更简洁,改造需要注意3个核心点:
- 原代码的
fin_output是普通字符串,多线程下拼接会有并发安全问题,改用线程安全的容器存储结果 - 遍历目录时,每遇到符合后缀要求的文件,就把该文件的扫描任务提交到线程池
- 增加计数工具等待所有任务执行完成,再统一输出结果
改造后完整代码
import java.io.File; import java.io.FileNotFoundException; import java.util.Scanner; import java.util.concurrent.*; public class FileSearch { // 线程安全的队列存储匹配到的文件路径 private final ConcurrentLinkedQueue<String> matchedFiles = new ConcurrentLinkedQueue<>(); // 线程池配置:IO密集型任务核心线程数设为CPU核心数2倍,可根据需求自行调整 private final ThreadPoolExecutor executor = new ThreadPoolExecutor( Runtime.getRuntime().availableProcessors() * 2, Runtime.getRuntime().availableProcessors() * 4, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>() ); public String searchInTextFiles(File dir, String searchWord) throws InterruptedException { File[] files = dir.listFiles(); if (files == null) return ""; // 计数器:统计所有需要执行的文件扫描任务数 CountDownLatch latch = new CountDownLatch(files.length); for (File f : files) { if (f.isDirectory()) { // 子目录递归处理,计数器减1因为当前是目录不是文件任务 latch.countDown(); searchInTextFiles(f, searchWord); } else if (f.getName().endsWith(".txt") || f.getName().endsWith(".html") || f.getName().endsWith(".htm")) { // 符合后缀要求的文件,提交扫描任务到线程池 executor.submit(new FileScanTask(f, searchWord, latch)); } else { // 不符合后缀的文件,直接减计数 latch.countDown(); } } // 等待所有文件扫描任务执行完成 latch.await(); // 关闭线程池 executor.shutdown(); // 汇总结果 StringBuilder result = new StringBuilder(); for (String path : matchedFiles) { result.append("FILE : ").append(path).append("\n"); } return result.toString(); } // 单文件扫描任务类 private class FileScanTask implements Runnable { private final File file; private final String searchWord; private final CountDownLatch latch; public FileScanTask(File file, String searchWord, CountDownLatch latch) { this.file = file; this.searchWord = searchWord; this.latch = latch; } @Override public void run() { try { Scanner sc = new Scanner(file); while (sc.hasNextLine()) { String line = sc.nextLine(); if (line.contains(searchWord)) { matchedFiles.add(file.getAbsolutePath()); // 找到匹配项就可以跳出循环,不用扫描整个文件 break; } } sc.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { // 不管任务执行成功还是失败,都要减计数 latch.countDown(); } } } // 测试方法 public static void main(String[] args) throws InterruptedException { FileSearch searcher = new FileSearch(); String result = searcher.searchInTextFiles(new File("替换为你要扫描的主目录路径"), "替换为你要搜索的目标词汇"); System.out.println(result); } }
核心改动说明
- 线程安全优化:用
ConcurrentLinkedQueue存储匹配结果,避免多线程下字符串拼接的并发覆盖问题 - 任务拆分:把单个文件的扫描逻辑封装为独立的
Runnable任务,提交到线程池异步执行,充分利用CPU资源 - 任务同步:用
CountDownLatch计数所有待执行的扫描任务,主线程等待所有任务完成后再汇总输出结果,避免程序提前结束丢失结果 - 性能优化:文件中找到匹配的词汇后直接跳出循环,不需要扫描整个文件,大幅提升大文件场景下的执行效率
内容的提问来源于stack exchange,提问作者NamitTheCoder
相关产品推荐
相关产品推荐

