抛出一个问题:如何遍历一个文件目录?
百度一下简直太多了,例如 https://www.cnblogs.com/helios-fz/p/11023205.html 小哥给了两个实现,一个单纯的循环,另一个就是递归的实现,然后就可以在控制台打印目录下所有的文件以及文件夹,以及文件夹下的文件及文件夹…有多少输出多少。
上面人家给出了大致的思路就是两种实现方式,但实际应用中,可就不仅仅是在控制台输出喽。
树形递归
有这样一个需求,要将某个文件目录下所有文件及文件夹(构成一颗展示树)展示到前台页面(web)中,设计一个对象来存储这种树形结构,采用递归遍历来构造出这样一个对象。
树形数据结构
这是一颗典型的树形结构,包含一个根节点(顶层目录)、包含一般节点(文件夹),包含叶子节点(文件),并且节点间有明确的上下级关系,这里我们只需要指导一个节点下包含的子节点即可,那么这样的树形结构设计也就明确了,如下设计
/**
* com.june.autotest.buz.LeftTree.java
* 2020年10月26日 下午5:51:20
*/
package com.june.autotest.buz;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.ToString;
/**
* 左侧树列表 LeftTree <br>
*
* @author junehappylove
* @version 1.0
*
*/
@Data
@ToString
public class LeftTree {
private String id;// 唯一索引
private String path;// 文件绝对路径
private String name;// 文件名称
private List<LeftTree> children;
public void add(LeftTree tree) {
if(children == null) {
children = new ArrayList<LeftTree>();
}
children.add(tree);
}
}
上面为对象的存储结构,基本属性已经注释,另外包含一个children
属性用于存储下级树,还提供一个添加add
下级对象的方法。
树形结构遍历
好了,存储结构搞定,那么如何由一个文件目录java.io.File导出呢?
那么就另起一个方法,进行逻辑的实现,参考上面小老弟的递归遍历文件目录方法:
public static void folderMethod2(String path) {
File file = new File(path);
if (file.exists()) {
File[] files = file.listFiles();
if (null != files) {
for (File file2 : files) {
if (file2.isDirectory()) {
System.out.println("文件夹:" + file2.getAbsolutePath());
folderMethod2(file2.getAbsolutePath());
} else {
System.out.println("文件:" + file2.getAbsolutePath());
}
}
}
} else {
System.out.println("文件不存在!");
}
}
这个基本思想就是看文件是否为文件夹,是的话就进入继续遍历。
然而从业务来讲,我们需要保留每一步的过程信息,并封装成LeftTree对象,下面是业务逻辑的封装过程,真正有用的代码:
/**
* 对外提供的转换接口
* @param path file的路径
*/
public LeftTree leftTree(String path) {
File tmp = new File(path);
LeftTree tree = new LeftTree();
int index = 0;
fromFile(index, tmp, tree);
return tree;
}
/**
* 将文件变成树对象,核心
* @param index 索引
* @param file 文件对象
* @param tree 所需的树对象
*/
private void fromFile(int index, File file, LeftTree tree) {
tree = file2Tree(index, file, tree);// 先将当前的file封装成树对象
if (file.exists()) {
File[] files = file.listFiles();
if (files != null) {
for (File tmp : files) {
index++;
if (tmp.isDirectory()) {
LeftTree ttmp = file2Tree(index, tmp);
tree.add(ttmp);// 新的ttmp放入当前tree中
fromFile(index, tmp, ttmp);// 递归构建
} else {
LeftTree ttmp = file2Tree(index, tmp);
tree.add(ttmp);
}
}
}
}
}
// 将当前的file转成tree对象
private LeftTree file2Tree(int index, File file, LeftTree tree) {
String id = index + "";
String path = file.getAbsolutePath();
String name = file.getName();
tree.setId(id);
tree.setName(name);
tree.setPath(path);
return tree;
}
// 根据file构建一颗新得树对象
private LeftTree file2Tree(int index, File file) {
String id = index + "";
String path = file.getAbsolutePath();
String name = file.getName();
LeftTree tree = new LeftTree();
tree.setId(id);
tree.setName(name);
tree.setPath(path);
return tree;
}
说明: index的作用,其实也没啥需要,给每个LeftTree一个标识而已,没有也没事。
要使用递归的前提条件:这个要特别注意了,使用递归必须要有终止条件,就是什么清况下要能够终止递归!
递归说白了就是个循环,但是跟for,while还有些不一样,for和while都是其内部控制的,而递归不是,递归用错了就会出现堆栈溢出,严重导致蓝屏;
使用递归尽量使用变量引用,减少参数传递,具体原因可以跟踪内存堆栈的使用查看,这里不展开分析。
链式递归
因为在同一个项目中遇到了这两种形式的递归表现,所以才写这篇文章记录一下!
上面的树形递归,简单点表述就是,一个节点下面包含若干个子节点,子节点下又包含字节的依此类推,直到没有子节点了。
类比,链式递归,就是一个节点下面挂着一个节点,直到没有节点为止,就像一个链儿。对链的遍历也用递归实现。
链式数据结构
其存储结构简单如下:
/**
* org.june.opcua.Node.java
* 2020-8-25 11:12:34
*/
package com.june.autotest.opcua;
import com.june.autotest.cache.NodeCacher;
import lombok.Data;
/**
* 基本单位是Node <br>
*
* @author junehappylove
* @version 1.0
*
*/
@Data
public class Node {
private String pid;// project标识
private Integer pk;// 唯一标识
private String value;// 当前值
public boolean isEnd() {
return getNext() == null;
}
// 下一个节点
public Node getNext() {
int next = this.pk + 1;
return NodeCacher.get(pid, next);
}
// 上一个节点
public Node getPrevious() {
if (this.pk == 0) {
return null;
}
int pre = this.pk - 1;
// 从缓存中去上一个节点
return NodeCacher.get(pid, pre);
}
public boolean isEnd() {
return getNext() == null;
}
// ......这里将没有用的数据和操作剔除了
}
上面用到了一个缓存器NodeCacher
,其作用就是将所有的Node数据缓存住(就是个Map)
然后在节点内部提供两个方法,获取下一个节点getNext
和获取上一个节点getPrevious
方法,另外还提供一个方法用于判断一个节点是不是最终的结束节点isEnd()
递归遍历链式结构
直接上代码,
public void run() {
try {
// 前置处理逻辑
// ....
// 递归逻辑
handler(node);
// 后置处理逻辑
// ....
} catch (Exception e) {
log.error("执行异常", e);
}
}
// 递归实现遍历Node节点
private void handler(Node node) throws Exception {
doHandle(node);// 操作当前节点
if (node.isEnd()) {
// 结束标志
return;
} else {
handler(node.getNext());
}
}
/**
* 业务操作需要
* @param node
* @throws Exception
*/
private void doHandle(Node node) throws Exception {
node.setCurrent(true);// 设置节点为当前运行节点
// to do something else
// 业务操作
node.setCurrent(false);// 将当前节点设置为非当前节点
}
上面代码已经经过简化处理,看似比树形递归简单些。
其递归思想一点儿没变,必须有停止条件!禁止无限递归调用