首先感谢czbk的老师,录制的视频,让我们有这么好的学习资料。……——
统计文件夹java文件的行数,首先想到的肯定是用递归的方法,因为文件夹下面可能包含文件夹,用递归的方法,代码容易写。(这和写简单的网络爬虫很像,一级页面下包含有二级页面的链接,二级页面又包含下一级的超链接)但是,用递归的方式有以下缺点:
- 如果文件夹层次很多,进而递归深度太大,容易栈溢出
- 用递归的方式,只能单线程执行,因为这一次的递归依赖于上一次的递归执行的结果
递归方式代码比较简单,如下:
public class LineCounterV1 { public static void main(String[] args) { String path = "D:/wsc/eclipse/myeclipse-workspace/MyLineCount"; int count = getAllJavaFilesLineCount(new File(path)); System.out.println("总行数:" + count); } /** * 使用递归实现统计这个文件夹中(包含子孙文件夹中的)的所有.java文件的总行数 * * @param dir * 文件夹 * @return */ private static int getAllJavaFilesLineCount(File dir) { int count = 0; for (File file : dir.listFiles()) { // 如果是.java文件,就统计行数 if (file.isFile() && file.getName().endsWith(".java")) { count += FileUtils.getLineCount(file); } // 如果是文件夹,就递归调用 else if (file.isDirectory()) { count += getAllJavaFilesLineCount(file); } } return count; } }
其中,取得某个文件行数的代码如下:
public class FileUtils { /** * 读取指定文件的内容,返回总行数 * * @param file * @return */ public static int getLineCount(File file) { if (!file.isFile()) { throw new IllegalArgumentException("请指定一个有效的文件对象!"); } try { BufferedReader reader = new BufferedReader(new FileReader(file)); int count = 0; while (reader.readLine() != null) { count++; } reader.close(); return count; } catch (Exception e) { throw new RuntimeException(e); } } }
------------------------------------------------------------------------------------------------------------------------------------------
第二种方法,使用任务队列的方法,这是个很实用的技巧,特记录学习。
任务队列底层是一种队列的数据结构,可以存放在内存中,也可以保存在数据库中(递归的过程中使用的数据只能放在内存中),另外,很多个工作线程组成所谓的任务处理器,可以多线程处理任务队列中的任务(和一般的生产者消费者模型不同,这里任务队列中的线程既是任务的消费者,也是任务的生产者--其中消费者对应的是统计某个文件夹下所有文件的代码行数,生产者对应的是如果某个文件夹下保护子文件夹则创建新的任务并加入到任务队列尾)。
任务队列涉及到的类有以下:
- 任务,这里指的是统计某个文件夹下的所有java文件的代码总行数
- 任务队列,提供取出队头任务和加入队尾任务的功能,这里底层使用的是LinkedList来模拟
- 处理线程,取出任务队列中的队头任务,并执行这个任务(按照面向对象原则,执行任务方法应该封装在任务类中而不是线程类中,因为只有任务类最清楚如何具体执行这个任务)
- 一个测试类,初始化时往任务队列中放入一个任务(即统计根文件夹这个目录下的代码行数),开启任务处理线程,等待2秒钟(视具体情况),输出统计结果
使用任务队列的好处有:
- 可以处理大数据量
- 借助数据库持久化任务队列,可以实现高可靠性-因为不依赖于内存,可以应对断电等情况
- 多线程处理,在数据量比较大时,速度快
Task.java
public class Task { // 最终统计的总行数的结果 private static int count = 0; // 待统计的文件夹 private File dir; public Task(File dir) { this.dir = dir; } /** * 办理任务: * * 遍历本文件夹中的所有子文件和字文件夹:<br> * a, 如果是.java文件,就统计行数。<br> * b, 如果是文件夹,就作为新的任务放到任务队列尾。 * */ public void execute() { System.out.println(Thread.currentThread() + " ---> 正在办理任务:" + dir.getPath()); for (File file : dir.listFiles()) { // 如果是.java文件,就统计行数 if (file.isFile() && file.getName().endsWith(".java")) { synchronized (Task.class) { // 注意同步问题 count += FileUtils.getLineCount(file); } } // 如果是文件夹,就作为新的任务放到任务队列尾 else if (file.isDirectory()) { Task task = new Task(file); TaskQueue.addToTail(task); } } } /** * 获取最终统计的总行数 * * @return */ public static int getCount() { return count; } }
TaskQueue.java
public class TaskQueue { private static LinkedList<Task> queue = new LinkedList<Task>(); /** * 把任务放到队列的尾巴上 * * @param task * @return */ // 注意同步问题 public static synchronized void addToTail(Task task) { queue.addLast(task); } /** * 从队列的头上取出一个任务,如果没有任务了,就返回null. */ // 注意同步问题 public static synchronized Task removeHead() { if (queue.size() > 0) { return queue.removeFirst(); } else { return null; } } }
Worker.java
public class Worker extends Thread { /** * 工作线程: 不断的从任务队列中取出一个任务并执行这个任务。 */ public void run() { System.out.println("==> 新的线程启动了:" + this); while (true) { try { // 如果有任务就办理 Task task = TaskQueue.removeHead(); if (task != null) { task.execute(); } // 如果没有任务,就休息一会再去看有没有任务 else { Thread.sleep(2); } } catch (InterruptedException e) { throw new RuntimeException(e); } } } }
测试类:
public class LineCounterV2 { public static void main(String[] args) throws Exception{ // 初始时只有一个任务(根目录) String path = "D:/wsc/eclipse/myeclipse-workspace/MyLineCount"; Task task = new Task(new File(path)); TaskQueue.addToTail(task); // 开始多线程执行 int threadCount = 5; for (int i = 0; i < threadCount; i++) { new Worker().start(); } // 等待一会去看统计结果 Thread.sleep(2000); int count = Task.getCount(); System.out.println("总行数:" + count); System.exit(0); } }