Java多线程遍历文件夹,广度遍历加多线程加深度遍历结合
复习IO操作,突然想写一个小工具,统计一下电脑里面的Java代码量还有注释率,最开始随手写了一个递归算法,遍历文件夹,比较简单,而且代码层次清晰,相对易于理解,代码如下:(完整代码贴在最后面,前面是功能实现代码)
-
public static void visitFile(File file) {
-
if (file != null) {
-
// 如果是文件夹
-
if (file.isDirectory()) {
-
// 统计文件夹下面的所有文件路径
-
File[] fls = file.listFiles();
-
// 如果父文件夹有内容
-
if (fls != null) {
-
// 那么遍历子文件
-
for (int i = 0; i < fls.length; i++) {
-
// 继续判断文件是文件夹还是文件,嵌套循环
-
visitFile(fls[i]);
-
}
-
}
-
} else// 如果是文件
-
{
-
// 判断文件名是不是.java类型
-
String fname = file.getName();
-
if (fname.endsWith(".java")) {
-
Sysotem.out.println("java文件:"+fname);
-
}
-
}
-
}
-
-
}
但是写成小工具后,在使用中我发现了它遍历速度还是比较慢的问题,递归算法本身运行效率低,占用空间也非常大,每一次调用都要出现方法压栈弹栈,系统开销大。所以我想把它改成非递归算法,我有两个想法:1.打开父文件夹(父亲)之后,遍历子文件夹(儿子),如果是目录就列出子文件夹的子文件夹(儿子的儿子),记录下来,但是不继续打开;如果遇到的是我需要的文件,那么就加入文件集合中,重复。代码如下:
-
File fl = this.file;//根文件(父亲)
-
ArrayList<File> flist = new ArrayList<File>();//文件夹目录列表1
-
ArrayList<File> flist2 = new ArrayList<File>();//文件夹目录列表2
-
ArrayList<File> tmp = null, next = null;//集合应用变量,tmp记录子文件夹的目录列表(儿子),next记录子文件夹的子文件夹列表(儿子的儿子)
-
flist.add(fl);//列表1记录根文件
-
// 广度遍历层数控制
-
int loop = 0;//控制循环层数
-
while (loop++ < 3) {// 此处只循环了三层
-
tmp = tmp == flist ? flist2 : flist;//此处比较绕,实现功能是tmp和next两个引用变量互换地址
-
next = next == flist2 ? flist : flist2;
-
for (int i = 0; i < tmp.size(); i++) {//遍历子文件夹
-
fl = tmp.get(i);
-
if (fl != null) {
-
if (fl.isDirectory()) {//如果遇到目录
-
File[] fls = fl.listFiles();
-
if (fls != null) {
-
next.addAll(Arrays.asList(fls));//将子文件夹的子文件夹目录列表一次全部加入next列表
-
}
-
} else {
-
if (fl.getName().endsWith(type)) {
-
papList.add(fl);//如果是需要的文件,就加入papList列表
-
}
-
}
-
}
-
}
-
tmp.clear();//清空子文件夹列表,因为已经遍历子文件夹结束,后面需要一个空的列表继续装东西
-
}
2.第二种思路是打开父文件夹后,遍历子文件夹,然后遇到目录就继续打开,直到没有目录才返回上一层,这个思路和递归遍历算法一样,看递归的算法更好理解,代码如下:
-
// 非递归深度遍历算法
-
void quickFind() throws IOException {
-
// 使用栈,进行深度遍历
-
Stack<java.io.File> stk = new Stack<File>();
-
stk.push(this.file);//父文件压栈
-
File f;
-
while (!stk.empty()) {//当栈不为空,就一直循环压栈出栈过程。
-
f = stk.pop();//弹出栈顶元素
-
if (f.isDirectory()) {//如果栈顶是目录
-
File[] fs = f.listFiles();//打开栈顶子目录
-
if (fs != null)
-
for (int i = 0; i < fs.length; i++) {
-
stk.push(fs[i]);//将栈顶子目录依次压栈
-
}
-
} else {
-
if (f.getName().endsWith(type)) {
-
// 记录所需文件的信息,加入集合
-
papList.add(f);
-
}
-
}
-
}
-
}
上面的两种非递归算法,速度上相差无几,相对于递归算法比较难于理解,但是速度真的快一点,而且占用内存比较小,如果使用递归,当递归层数比较多的时候对系统资源消耗巨大,甚至会造成jvm崩溃,非递归算法没有这个隐患,深度遍历和广度遍历在遍历很多文件时,深度遍历稍微占优势,速度会快一点,但是数据有浮动。后面我又复习到多线程,我就想把多线程加进去,会不会更快。然后我就建立了一个线程池:
-
// 创建线程池,一共THREAD_COUNT个线程可以使用
-
ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);//新建固定线程数的线程池
-
-
for (File file : next) {
-
pool.submit(new FileThread(file, type));//提交对象到线程池,FileThread类是我自定义的内部类,重写了Runnable接口中的run方法。
-
}
-
pool.shutdown();//结束
-
// 必须等到所有线程结束才可以让主线程退出,不然就一直阻塞
-
while (!pool.isTerminated())
-
;
线程池的好处是统一管理线程,不用一直开辟新的线程,开辟线程很消耗系统资源,线程池里面的线程可以循环使用,程序结束了再释放,适用于频繁切换任务的情况,在Tcp/ip网络编程中常见。加入多线程我也有两种想法,1.我先想到多线程就是几个兄弟一起干活,速度肯定快,所以我每遍历一个文件夹就开辟一个新的线程,代码如下:
-
void judge(File f) {
-
-
if (f != null) {
-
if (f.isDirectory()) {
-
// 如果是目录
-
File[] fs = f.listFiles();
-
if (fs != null)
-
FileOP.BigFileList.addAll(Arrays.asList(fs));
-
// 一起加到BigFileList中,前面有一个for循环遍历BigFileList,遍历一次开辟一个新线程
-
} else {
-
if (f.getName().endsWith(type)) {
-
FileOP.papList.add(f);
-
// 我们要的文件记录下来
-
}
-
}
-
}
-
}
但是想法很美好,现实很残酷,这种方法速度比递归算法还要慢,开辟新线程(此处还没有应用线程池,每次都new Thread();)的时间,加上垃圾回收的时间远超过递归算法遍历文件夹的时间。而且多线程也并不是可以无限个,一般来说CPU大多只支持四线程,但是线程数大于四时,cpu通过调度算法分配程序执行的时间,常见先进先出,短作业优先,时间片轮转调度,高优先权调度算法,我一般设置最大线程数是CPU支持线程数的3倍,根据我实际测试,设置成100个线程比设置成12个线程,程序执行时间没有短多少,反而在CPU占用率高的时候100线程更慢。
所以我又想,怎么才能发挥多线程的优势呢,首先肯定要把一个任务分成多个任务,这也有两个思路:1.先用递归深度遍历算法遍历文件夹,当遇到比较大的文件夹,比如说包含1000个子文件夹就记录下来,然后跳过继续遍历其他的文件夹,此时主线程有一个while循环一直在检查有没有新的大文件夹出现,如果有就开一个新线程去遍历大文件夹,代码如下:
-
void findAll(File f) {
-
if (f != null) {
-
-
if (f.isDirectory()) {
-
// 如果是目录
-
File[] fs = f.listFiles();
-
if (fs == null) {
-
return;
-
}
-
-
* if (fs.length > FileOP.THREAD_COUNT * 100) {//
-
* 当文件夹的目录数量大于线程数的百倍,记录下来,待会用多线程慢慢数 FileOP.BigFileList.add(f);
-
* // 这记录的都是后面要用多线程来数一数的 } else
-
{
-
for (int i = 0; i < fs.length; i++) {
-
findAll(fs[i]);
-
// 如果文件数少,就递归一下
-
}
-
}
-
} else {
-
// 需要的文件放进pap集合
-
if (f.getName().endsWith(type)) {
-
FileOP.papList.add(f);
-
}
-
}
-
}
-
}
实际效果比不上单纯的递归算法速度快,难受,因为我记录的文件夹虽然是“大文件夹”,但是可能并不深,递归一两层就结束了,这时候开新线程消耗更大,所以我就想到自上而下的分配任务,比如说我们让程序遍历C 盘所有的Java文件,程序可以先获取C盘根目录列表,然后开辟线程池,每一个线程执行一个子目录的遍历,遍历子文件夹时换成非递归深度遍历算法,算法如下:
-
package com.ycs;
-
-
import java.io.File;
-
import java.io.FileInputStream;
-
import java.io.IOException;
-
import java.io.InputStream;
-
import java.math.BigDecimal;
-
import java.util.ArrayList;
-
import java.util.Arrays;
-
import java.util.Stack;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
-
public class FileList {
-
// 控制线程数,最优选择是处理器线程数*3,本机处理器是4线程
-
private final static int THREAD_COUNT = 12;
-
// 线程共享数据,保存所有的type文件
-
private ArrayList<File> papList = new ArrayList<File>();
-
// 保存文件附加信息
-
private ArrayList<String> contenList = new ArrayList<String>();
-
// 当前文件或者目录
-
private File file;
-
-
// 所需的文件类型
-
private String type;
-
-
public FileList() {
-
super();
-
// TODO Auto-generated constructor stub
-
}
-
-
public FileList(String f, String type) {
-
super();
-
this.file = new File(f);
-
this.type = type;
-
}
-
-
public ArrayList<String> getContenList() {
-
return contenList;
-
}
-
-
// 内部类继承runnable接口,实现多线程
-
class FileThread implements Runnable {
-
private File file;
-
private String type;
-
-
public FileThread(File file, String type) {
-
super();
-
this.file = file;
-
this.type = type;
-
}
-
-
public FileThread() {
-
super();
-
// TODO Auto-generated cosnstructor stub
-
}
-
-
@Override
-
public void run() {
-
try {
-
quickFind();
-
} catch (IOException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
}
-
-
// 非递归深度遍历算法
-
void quickFind() throws IOException {
-
// 使用栈,进行深度遍历
-
Stack<java.io.File> stk = new Stack<File>();
-
stk.push(this.file);
-
File f;
-
while (!stk.empty()) {
-
f = stk.pop();
-
if (f.isDirectory()) {
-
File[] fs = f.listFiles();
-
if (fs != null)
-
for (int i = 0; i < fs.length; i++) {
-
stk.push(fs[i]);
-
}
-
} else {
-
if (f.getName().endsWith(type)) {
-
// 记录所需文件的信息
-
papList.add(f);
-
}
-
}
-
}
-
}
-
}
-
-
public ArrayList<File> getPapList() {
-
// 外部接口,传递遍历结果
-
return papList;
-
}
-
-
// 深度遍历算法加调用线程池
-
void File() {
-
File fl = this.file;
-
ArrayList<File> flist = new ArrayList<File>();
-
ArrayList<File> flist2 = new ArrayList<File>();
-
ArrayList<File> tmp = null, next = null;
-
flist.add(fl);
-
// 广度遍历层数控制
-
int loop = 0;
-
while (loop++ < 3) {// 最优循环层数是3层,多次实验得出
-
tmp = tmp == flist ? flist2 : flist;
-
next = next == flist2 ? flist : flist2;
-
for (int i = 0; i < tmp.size(); i++) {
-
fl = tmp.get(i);
-
if (fl != null) {
-
if (fl.isDirectory()) {
-
File[] fls = fl.listFiles();
-
if (fls != null) {
-
next.addAll(Arrays.asList(fls));
-
}
-
} else {
-
if (fl.getName().endsWith(type)) {
-
papList.add(fl);
-
}
-
}
-
}
-
}
-
tmp.clear();
-
}
-
-
// 创建线程池,一共THREAD_COUNT个线程可以使用
-
ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
-
-
for (File file : next) {
-
pool.submit(new FileThread(file, type));
-
}
-
pool.shutdown();
-
// 必须等到所有线程结束才可以让主线程退出,不然就一直阻塞
-
while (!pool.isTerminated())
-
;
-
}
-
-
void info(File file) throws IOException {
-
InputStream inputStream = new FileInputStream(file);
-
byte[] chs = new byte[(int) file.length()];
-
inputStream.read(chs);
-
inputStream.close();
-
String javaCode = new String(chs);
-
String[] lines = javaCode.split("\n");
-
int find = lines.length;// 实际代码行数
-
int counts = find;// 加上注释的行数
-
int zhushi = 0;
-
for (int i = 0; i < lines.length; i++) {
-
lines[i] = lines[i].trim();
-
if (lines[i].length() == 0) {
-
counts--;
-
find--;
-
} else if (lines[i].startsWith("//")) {
-
// System.out.println("单行注释:"+lines[i]);
-
find--;
-
zhushi++;
-
} else if (lines[i].indexOf("/*") != -1) {
-
find--;
-
zhushi++;
-
while (lines[i].indexOf("*/") == -1) {
-
// System.out.println(lines[i]);
-
find--;
-
zhushi++;
-
i++;
-
}
-
}
-
}
-
-
double zc = ((double) zhushi / counts) * 100;
-
BigDecimal b = new BigDecimal(zc);
-
double zcc = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
-
String s = file.getName() + "代码行数:" + find + "\t注释行数:" + zhushi + "\t 注释率:" + zcc + "%";
-
-
contenList.add(s);
-
}
-
}
这一次果然快了很多,但是幅度不大,通过分析我发现然来C盘根目录的文件夹也不是每一个大小都一样的,有一些文件夹里面文件特别多,有一些就很少,而且只遍历C盘根目录,然后再调用多线程,可能文件夹数量没有线程数多,我应该多遍历几层,再调用多线程,多次实验后我发现只有遍历三层才是最快的,原理不明,但是遍历一层、两层、四层、五层程序执行时间都比较长,三层是一个神奇的点。最后的实验结果:多线程遍历,文件越多约占优势,16万个文件,单线程递归算法需要8-9秒,单线程非递归需要7-8秒,三种结合只需要3-4秒,而且在文件数比较少的时候,此方法也有比较大幅度的提升,快一两百毫秒。
最后贴上程序运行图:
嗯,贴不了。。。
完整代码就是上面那个,创建一个对象,给构造函数传入文件路径(String)和文件类型(String)就可以了。
小白之作,轻喷轻喷
原文地址:https://blog.csdn.net/qq_24833939/article/details/79222444
Java多线程遍历文件夹,广度遍历加多线程加深度遍历结合的更多相关文章
- Java学习随笔3:遍历文件夹及文件的读取和写入
import java.io.File; /** * 遍历文件夹 */ public class ScannerFile { public static void main(String[] args ...
- java 遍历文件夹里的文件
Java遍历文件夹的2种方法: A.不使用递归: import java.io.File; import java.util.LinkedList; public class FileSystem { ...
- JAVA 遍历文件夹下的所有文件
JAVA 遍历文件夹下的所有文件(递归调用和非递归调用) 1.不使用递归的方法调用. public void traverseFolder1(String path) { int fileNum = ...
- JAVA 遍历文件夹下的所有文件(递归调用和非递归调用)
JAVA 遍历文件夹下的所有文件(递归调用和非递归调用) 1.不使用递归的方法调用. public void traverseFolder1(String path) { int fileNum = ...
- java学习笔记——IO部分(遍历文件夹)
用File类写的一个简单的工具,遍历文件夹,获取该文件夹下的所以文件(含子目录下的文件)和文件大小: /** * 列出指定目录下(包含其子目录)的所有文件 * @author syskey * */ ...
- Java File类应用:递归遍历文件夹和递归删除文件
要求: 1)采用递归遍历文件夹下的所有文件,包括子文件夹下的文件 2)采用递归删除文件下的所有文件 注意: 以下递归删除文件的方法,只能删除文件,所有的文件夹都还会存在 若要删除正文文件夹,可以在递归 ...
- java中File类应用:遍历文件夹下所有文件
练习: 要求指定文件夹下的所有文件,包括子文件夹下的文件 代码: package 遍历文件夹所有文件; import java.io.File; public class Test { public ...
- Java遍历文件夹下的所以文件
利用Java递归遍历文件夹下的所以文件,然后对文件进行其他的操作.如:对文件进行重命名,对某一类文件进行重编码.可以对某一工程下的全部.java文件进行转码成utf-8等 代码如下,这里只对文件进行重 ...
- 02 File类的方法练习——遍历文件夹
思路 需要遍历的文件夹 File 使用listFile列出下级文件及文件夹 判断得到的list是否为空,为空则输出当前文件夹名称 如果不为空,逐个判断是文件还是文件夹 如果是文件,输出文件名 如果是文 ...
随机推荐
- Flask – SQLAlchemy成员增加
目录 简介 结构 展示 技术 运行 代码 创建数据库表单 views视图 home主页 添加成员addnew.html 展示页show_all 简介 结构 $ tree -I "__pyca ...
- 2019阿里云开年Hi购季云通信分会场全攻略!
2019阿里云云上Hi购季活动已经于2月25日正式开启,从已开放的活动页面来看,活动分为三个阶段: 2月25日-3月04日的活动报名阶段.3月04日-3月16日的新购满返+5折抢购阶段.3月16日-3 ...
- leetcode 31-40 easy
38.Count and Say The count-and-say sequence is the sequence of integers with the first five terms as ...
- 全球城市群Megalopolis
Megalopolis From Wikipedia, the free encyclopedia (Redirected from Megalopolis (city type)) &quo ...
- SQL Server删除用户失败的解决方法
在删除SQL Server用户时,有时会报错:Microsoft SQL Server错误: 15138删除对于用户失败,数据库主体在该数据库中拥有架构,无法删除.删除 对于 用户“*****”失败. ...
- Tiles Framework
tiles framework 详解tiles framework 详解 就是一个页面模版引擎.可以渲染页面,属于视图层. 下面给你拷贝一份详细的tiles介绍,你可以初步了解一下. Tiles框架特 ...
- 修改Eclipse自动换行长度
使用Ctrl+Shift+F自动格式化代码的时候,有时候折行太多反而让代码看起来更乱,不容易阅读. 解决办法: Window-->Preferences-->Java-->Code ...
- android非硬件加速绘制简单流程
这里的硬件加速是指openGL + GPU 如果不适用硬件加速: 1 ViewRootImpl.java draw:if (!dirty.isEmpty() || mIsAnimating || ac ...
- 移动端的vh 和 vw简介和使用场景
vw 相对于视窗的宽度:视窗宽度是100vw:vh则类似,是相对于视窗的高度,视窗高度是100vh. 这里的视窗指的又是啥? 是浏览器内部宽度大小(window.innerWidth)? 是整个浏览器 ...
- laravel 图片上传 intervention/image
1. composer require intervention/image 2). 修改 app/config/app.php 添加 ServiceProvider: // 将下面代码添加到 pro ...