You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }
}
核心改动说明
  1. 线程安全优化:用ConcurrentLinkedQueue存储匹配结果,避免多线程下字符串拼接的并发覆盖问题
  2. 任务拆分:把单个文件的扫描逻辑封装为独立的Runnable任务,提交到线程池异步执行,充分利用CPU资源
  3. 任务同步:用CountDownLatch计数所有待执行的扫描任务,主线程等待所有任务完成后再汇总输出结果,避免程序提前结束丢失结果
  4. 性能优化:文件中找到匹配的词汇后直接跳出循环,不需要扫描整个文件,大幅提升大文件场景下的执行效率

内容的提问来源于stack exchange,提问作者NamitTheCoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 02:00:00