有趣的递归,让遍历变简洁

抛出一个问题:如何遍历一个文件目录?
百度一下简直太多了,例如 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);// 将当前节点设置为非当前节点
	}

上面代码已经经过简化处理,看似比树形递归简单些。
其递归思想一点儿没变,必须有停止条件!禁止无限递归调用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

junehappylove

急急如律令,buibui~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值