zoukankan      html  css  js  c++  java
  • Java多线程遍历文件夹,广度遍历加多线程加深度遍历结合

    复习IO操作,突然想写一个小工具,统计一下电脑里面的Java代码量还有注释率,最开始随手写了一个递归算法,遍历文件夹,比较简单,而且代码层次清晰,相对易于理解,代码如下:(完整代码贴在最后面,前面是功能实现代码)

    1. public static void visitFile(File file) {
    2. if (file != null) {
    3. // 如果是文件夹
    4. if (file.isDirectory()) {
    5. // 统计文件夹下面的所有文件路径
    6. File[] fls = file.listFiles();
    7. // 如果父文件夹有内容
    8. if (fls != null) {
    9. // 那么遍历子文件
    10. for (int i = 0; i < fls.length; i++) {
    11. // 继续判断文件是文件夹还是文件,嵌套循环
    12. visitFile(fls[i]);
    13. }
    14. }
    15. } else// 如果是文件
    16. {
    17. // 判断文件名是不是.java类型
    18. String fname = file.getName();
    19. if (fname.endsWith(".java")) {
    20. Sysotem.out.println("java文件:"+fname);
    21. }
    22. }
    23. }
    24. }


    但是写成小工具后,在使用中我发现了它遍历速度还是比较慢的问题,递归算法本身运行效率低,占用空间也非常大,每一次调用都要出现方法压栈弹栈,系统开销大。所以我想把它改成非递归算法,我有两个想法:1.打开父文件夹(父亲)之后,遍历子文件夹(儿子),如果是目录就列出子文件夹的子文件夹(儿子的儿子),记录下来,但是不继续打开;如果遇到的是我需要的文件,那么就加入文件集合中,重复。代码如下:

    1. File fl = this.file;//根文件(父亲)
    2. ArrayList<File> flist = new ArrayList<File>();//文件夹目录列表1
    3. ArrayList<File> flist2 = new ArrayList<File>();//文件夹目录列表2
    4. ArrayList<File> tmp = null, next = null;//集合应用变量,tmp记录子文件夹的目录列表(儿子),next记录子文件夹的子文件夹列表(儿子的儿子)
    5. flist.add(fl);//列表1记录根文件
    6. // 广度遍历层数控制
    7. int loop = 0;//控制循环层数
    8. while (loop++ < 3) {// 此处只循环了三层
    9. tmp = tmp == flist ? flist2 : flist;//此处比较绕,实现功能是tmp和next两个引用变量互换地址
    10. next = next == flist2 ? flist : flist2;
    11. for (int i = 0; i < tmp.size(); i++) {//遍历子文件夹
    12. fl = tmp.get(i);
    13. if (fl != null) {
    14. if (fl.isDirectory()) {//如果遇到目录
    15. File[] fls = fl.listFiles();
    16. if (fls != null) {
    17. next.addAll(Arrays.asList(fls));//将子文件夹的子文件夹目录列表一次全部加入next列表
    18. }
    19. } else {
    20. if (fl.getName().endsWith(type)) {
    21. papList.add(fl);//如果是需要的文件,就加入papList列表
    22. }
    23. }
    24. }
    25. }
    26. tmp.clear();//清空子文件夹列表,因为已经遍历子文件夹结束,后面需要一个空的列表继续装东西
    27. }



      

    2.第二种思路是打开父文件夹后,遍历子文件夹,然后遇到目录就继续打开,直到没有目录才返回上一层,这个思路和递归遍历算法一样,看递归的算法更好理解,代码如下:

    1. // 非递归深度遍历算法
    2. void quickFind() throws IOException {
    3. // 使用栈,进行深度遍历
    4. Stack<java.io.File> stk = new Stack<File>();
    5. stk.push(this.file);//父文件压栈
    6. File f;
    7. while (!stk.empty()) {//当栈不为空,就一直循环压栈出栈过程。
    8. f = stk.pop();//弹出栈顶元素
    9. if (f.isDirectory()) {//如果栈顶是目录
    10. File[] fs = f.listFiles();//打开栈顶子目录
    11. if (fs != null)
    12. for (int i = 0; i < fs.length; i++) {
    13. stk.push(fs[i]);//将栈顶子目录依次压栈
    14. }
    15. } else {
    16. if (f.getName().endsWith(type)) {
    17. // 记录所需文件的信息,加入集合
    18. papList.add(f);
    19. }
    20. }
    21. }
    22. }


    上面的两种非递归算法,速度上相差无几,相对于递归算法比较难于理解,但是速度真的快一点,而且占用内存比较小,如果使用递归,当递归层数比较多的时候对系统资源消耗巨大,甚至会造成jvm崩溃,非递归算法没有这个隐患,深度遍历和广度遍历在遍历很多文件时,深度遍历稍微占优势,速度会快一点,但是数据有浮动。后面我又复习到多线程,我就想把多线程加进去,会不会更快。然后我就建立了一个线程池:

    1. // 创建线程池,一共THREAD_COUNT个线程可以使用
    2. ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);//新建固定线程数的线程池
    3. for (File file : next) {
    4. pool.submit(new FileThread(file, type));//提交对象到线程池,FileThread类是我自定义的内部类,重写了Runnable接口中的run方法。
    5. }
    6. pool.shutdown();//结束
    7. // 必须等到所有线程结束才可以让主线程退出,不然就一直阻塞
    8. while (!pool.isTerminated())
    9. ;

    线程池的好处是统一管理线程,不用一直开辟新的线程,开辟线程很消耗系统资源,线程池里面的线程可以循环使用,程序结束了再释放,适用于频繁切换任务的情况,在Tcp/ip网络编程中常见。加入多线程我也有两种想法,1.我先想到多线程就是几个兄弟一起干活,速度肯定快,所以我每遍历一个文件夹就开辟一个新的线程,代码如下:

    1. void judge(File f) {
    2. if (f != null) {
    3. if (f.isDirectory()) {
    4. // 如果是目录
    5. File[] fs = f.listFiles();
    6. if (fs != null)
    7. FileOP.BigFileList.addAll(Arrays.asList(fs));
    8. // 一起加到BigFileList中,前面有一个for循环遍历BigFileList,遍历一次开辟一个新线程
    9. } else {
    10. if (f.getName().endsWith(type)) {
    11. FileOP.papList.add(f);
    12. // 我们要的文件记录下来
    13. }
    14. }
    15. }
    16. }
    但是想法很美好,现实很残酷,这种方法速度比递归算法还要慢,开辟新线程(此处还没有应用线程池,每次都new Thread();)的时间,加上垃圾回收的时间远超过递归算法遍历文件夹的时间。而且多线程也并不是可以无限个,一般来说CPU大多只支持四线程,但是线程数大于四时,cpu通过调度算法分配程序执行的时间,常见先进先出,短作业优先,时间片轮转调度,高优先权调度算法,我一般设置最大线程数是CPU支持线程数的3倍,根据我实际测试,设置成100个线程比设置成12个线程,程序执行时间没有短多少,反而在CPU占用率高的时候100线程更慢。

        所以我又想,怎么才能发挥多线程的优势呢,首先肯定要把一个任务分成多个任务,这也有两个思路:1.先用递归深度遍历算法遍历文件夹,当遇到比较大的文件夹,比如说包含1000个子文件夹就记录下来,然后跳过继续遍历其他的文件夹,此时主线程有一个while循环一直在检查有没有新的大文件夹出现,如果有就开一个新线程去遍历大文件夹,代码如下:

    1. void findAll(File f) {
    2. if (f != null) {
    3. if (f.isDirectory()) {
    4. // 如果是目录
    5. File[] fs = f.listFiles();
    6. if (fs == null) {
    7. return;
    8. }
    9. * if (fs.length > FileOP.THREAD_COUNT * 100) {//
    10. * 当文件夹的目录数量大于线程数的百倍,记录下来,待会用多线程慢慢数 FileOP.BigFileList.add(f);
    11. * // 这记录的都是后面要用多线程来数一数的 } else
    12. {
    13. for (int i = 0; i < fs.length; i++) {
    14. findAll(fs[i]);
    15. // 如果文件数少,就递归一下
    16. }
    17. }
    18. } else {
    19. // 需要的文件放进pap集合
    20. if (f.getName().endsWith(type)) {
    21. FileOP.papList.add(f);
    22. }
    23. }
    24. }
    25. }
    实际效果比不上单纯的递归算法速度快,难受,因为我记录的文件夹虽然是“大文件夹”,但是可能并不深,递归一两层就结束了,这时候开新线程消耗更大,所以我就想到自上而下的分配任务,比如说我们让程序遍历C 盘所有的Java文件,程序可以先获取C盘根目录列表,然后开辟线程池,每一个线程执行一个子目录的遍历,遍历子文件夹时换成非递归深度遍历算法,算法如下:

    1. package com.ycs;
    2. import java.io.File;
    3. import java.io.FileInputStream;
    4. import java.io.IOException;
    5. import java.io.InputStream;
    6. import java.math.BigDecimal;
    7. import java.util.ArrayList;
    8. import java.util.Arrays;
    9. import java.util.Stack;
    10. import java.util.concurrent.ExecutorService;
    11. import java.util.concurrent.Executors;
    12. public class FileList {
    13. // 控制线程数,最优选择是处理器线程数*3,本机处理器是4线程
    14. private final static int THREAD_COUNT = 12;
    15. // 线程共享数据,保存所有的type文件
    16. private ArrayList<File> papList = new ArrayList<File>();
    17. // 保存文件附加信息
    18. private ArrayList<String> contenList = new ArrayList<String>();
    19. // 当前文件或者目录
    20. private File file;
    21. // 所需的文件类型
    22. private String type;
    23. public FileList() {
    24. super();
    25. // TODO Auto-generated constructor stub
    26. }
    27. public FileList(String f, String type) {
    28. super();
    29. this.file = new File(f);
    30. this.type = type;
    31. }
    32. public ArrayList<String> getContenList() {
    33. return contenList;
    34. }
    35. // 内部类继承runnable接口,实现多线程
    36. class FileThread implements Runnable {
    37. private File file;
    38. private String type;
    39. public FileThread(File file, String type) {
    40. super();
    41. this.file = file;
    42. this.type = type;
    43. }
    44. public FileThread() {
    45. super();
    46. // TODO Auto-generated cosnstructor stub
    47. }
    48. @Override
    49. public void run() {
    50. try {
    51. quickFind();
    52. } catch (IOException e) {
    53. // TODO Auto-generated catch block
    54. e.printStackTrace();
    55. }
    56. }
    57. // 非递归深度遍历算法
    58. void quickFind() throws IOException {
    59. // 使用栈,进行深度遍历
    60. Stack<java.io.File> stk = new Stack<File>();
    61. stk.push(this.file);
    62. File f;
    63. while (!stk.empty()) {
    64. f = stk.pop();
    65. if (f.isDirectory()) {
    66. File[] fs = f.listFiles();
    67. if (fs != null)
    68. for (int i = 0; i < fs.length; i++) {
    69. stk.push(fs[i]);
    70. }
    71. } else {
    72. if (f.getName().endsWith(type)) {
    73. // 记录所需文件的信息
    74. papList.add(f);
    75. }
    76. }
    77. }
    78. }
    79. }
    80. public ArrayList<File> getPapList() {
    81. // 外部接口,传递遍历结果
    82. return papList;
    83. }
    84. // 深度遍历算法加调用线程池
    85. void File() {
    86. File fl = this.file;
    87. ArrayList<File> flist = new ArrayList<File>();
    88. ArrayList<File> flist2 = new ArrayList<File>();
    89. ArrayList<File> tmp = null, next = null;
    90. flist.add(fl);
    91. // 广度遍历层数控制
    92. int loop = 0;
    93. while (loop++ < 3) {// 最优循环层数是3层,多次实验得出
    94. tmp = tmp == flist ? flist2 : flist;
    95. next = next == flist2 ? flist : flist2;
    96. for (int i = 0; i < tmp.size(); i++) {
    97. fl = tmp.get(i);
    98. if (fl != null) {
    99. if (fl.isDirectory()) {
    100. File[] fls = fl.listFiles();
    101. if (fls != null) {
    102. next.addAll(Arrays.asList(fls));
    103. }
    104. } else {
    105. if (fl.getName().endsWith(type)) {
    106. papList.add(fl);
    107. }
    108. }
    109. }
    110. }
    111. tmp.clear();
    112. }
    113. // 创建线程池,一共THREAD_COUNT个线程可以使用
    114. ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
    115. for (File file : next) {
    116. pool.submit(new FileThread(file, type));
    117. }
    118. pool.shutdown();
    119. // 必须等到所有线程结束才可以让主线程退出,不然就一直阻塞
    120. while (!pool.isTerminated())
    121. ;
    122. }
    123. void info(File file) throws IOException {
    124. InputStream inputStream = new FileInputStream(file);
    125. byte[] chs = new byte[(int) file.length()];
    126. inputStream.read(chs);
    127. inputStream.close();
    128. String javaCode = new String(chs);
    129. String[] lines = javaCode.split(" ");
    130. int find = lines.length;// 实际代码行数
    131. int counts = find;// 加上注释的行数
    132. int zhushi = 0;
    133. for (int i = 0; i < lines.length; i++) {
    134. lines[i] = lines[i].trim();
    135. if (lines[i].length() == 0) {
    136. counts--;
    137. find--;
    138. } else if (lines[i].startsWith("//")) {
    139. // System.out.println("单行注释:"+lines[i]);
    140. find--;
    141. zhushi++;
    142. } else if (lines[i].indexOf("/*") != -1) {
    143. find--;
    144. zhushi++;
    145. while (lines[i].indexOf("*/") == -1) {
    146. // System.out.println(lines[i]);
    147. find--;
    148. zhushi++;
    149. i++;
    150. }
    151. }
    152. }
    153. double zc = ((double) zhushi / counts) * 100;
    154. BigDecimal b = new BigDecimal(zc);
    155. double zcc = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    156. String s = file.getName() + "代码行数:" + find + " 注释行数:" + zhushi + " 注释率:" + zcc + "%";
    157. contenList.add(s);
    158. }
    159. }
    这一次果然快了很多,但是幅度不大,通过分析我发现然来C盘根目录的文件夹也不是每一个大小都一样的,有一些文件夹里面文件特别多,有一些就很少,而且只遍历C盘根目录,然后再调用多线程,可能文件夹数量没有线程数多,我应该多遍历几层,再调用多线程,多次实验后我发现只有遍历三层才是最快的,原理不明,但是遍历一层、两层、四层、五层程序执行时间都比较长,三层是一个神奇的点。最后的实验结果:多线程遍历,文件越多约占优势,16万个文件,单线程递归算法需要8-9秒,单线程非递归需要7-8秒,三种结合只需要3-4秒,而且在文件数比较少的时候,此方法也有比较大幅度的提升,快一两百毫秒。

    最后贴上程序运行图:




    嗯,贴不了。。。

    完整代码就是上面那个,创建一个对象,给构造函数传入文件路径(String)和文件类型(String)就可以了。

    小白之作,轻喷轻喷



    原文地址:https://blog.csdn.net/qq_24833939/article/details/79222444
  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/jpfss/p/11099938.html
Copyright © 2011-2022 走看看