代码实战之道——Java如何构建树结构

简介: Java如何构建树结构

1、前言

在开发的过程中很多业务场景需要一个树形结构的结果集进行前端展示,也可以理解为是一个无限父子结构。而实现方式也是有很多种,我这里提供递归和非递归两种实现方法吧。依赖引入了lombok

2、递归构建树结构

树结构基类,有需要构建树结构的类就继承

@DatapublicclassTreeNode {
/*** 节点id*/privateIntegerid;
/*** 节点名称*/privateStringname;
/***  父节点id*/privateIntegerparentId;
/*** 子节点*/privateList<TreeNode>childList;
publicTreeNode() {
    }
publicTreeNode(Integerid, Stringname, IntegerparentId) {
this.id=id;
this.name=name;
this.parentId=parentId;
    }
}

工具类:搭配注释很容易看懂

importcom.example.tree.entity.TreeNode;
importorg.springframework.util.CollectionUtils;
importjava.util.*;
/*** @description:* @author: LoneWalker* @create: 2022-11-28**/publicclassTreeUtil {
/*** 获取所有的根节点*/publicstatic<TextendsTreeNode>List<T>getRootNode(List<T>treeNodeList,Set<Integer>nodeIdSet){
List<T>rootNodeList=newArrayList<>();
for (TtreeNode : treeNodeList) {
if (0==treeNode.getParentId() ||null==treeNode.getParentId()){
rootNodeList.add(treeNode);
nodeIdSet.add(treeNode.getId());
            }
        }
returnrootNodeList;
    }
/*** 构建树*/publicstatic<TextendsTreeNode>TbuildChildTree(TrootNode,List<T>treeNodeList,Set<Integer>nodeIdSet){
List<T>childNodeList=newArrayList<>();
for (TtreeNode : treeNodeList) {
if (!nodeIdSet.contains(treeNode.getId()) &&treeNode.getParentId().equals(rootNode.getId())){
nodeIdSet.add(treeNode.getId());
//这里相当于把当前节点又作为父节点 递归查询子节点childNodeList.add(buildChildTree(treeNode,treeNodeList,nodeIdSet));
            }
        }
rootNode.setChildList((List<TreeNode>) childNodeList);
returnrootNode;
    }
publicstatic<TextendsTreeNode>List<T>buildTree(List<T>treeNodeList){
if (CollectionUtils.isEmpty(treeNodeList)){
returnCollections.emptyList();
        }
//用来保存每一棵完整的树List<T>treeNodes=newArrayList<T>();
//用来保存已装填过的树节点ID 防止重复遍历Set<Integer>nodeIdSet=newHashSet<>();
for (Troot : getRootNode(treeNodeList,nodeIdSet)) {
TtreeNode=buildChildTree(root, treeNodeList,nodeIdSet);
treeNodes.add(treeNode);
        }
returntreeNodes;
    }
}

测试

@RestControllerpublicclassTreeController {
@GetMapping("/listTree")
publicList<TreeNode>listTree(){
//模拟数据List<TreeNode>treeNodeList=newArrayList<>();
treeNodeList.add(newTreeNode(1,"顶级节点A",0));
treeNodeList.add(newTreeNode(2,"顶级节点B",0));
treeNodeList.add(newTreeNode(3,"二级节点A1",1));
treeNodeList.add(newTreeNode(4,"二级结点B1",2));
treeNodeList.add(newTreeNode(5,"二级节点B2",2));
treeNodeList.add(newTreeNode(6,"三级节点A1-1",3));
treeNodeList.add(newTreeNode(7,"三级节点A1-2",3));
treeNodeList.add(newTreeNode(8,"四级节点A1-1-1",6));
treeNodeList.add(newTreeNode(9,"四级节点A1-1-2",6));
treeNodeList.add(newTreeNode(10,"五级节点A1-1-1-1",8));
longstart=System.currentTimeMillis();
List<TreeNode>treeNodes=TreeUtil.buildTree(treeNodeList);
System.out.println("耗时:"+(System.currentTimeMillis() -start));
returntreeNodes;
    }
}

3、非递归构建树

 

publicstatic<TextendsTreeNode>List<T>buildTree(List<T>treeNodeList){
List<T>rootNodeList=newArrayList<>();
//移除根数据rootNodeList=treeNodeList.stream().filter(d->d.getParentId() ==null||d.getParentId() ==0).collect(Collectors.toList());
treeNodeList.removeAll(rootNodeList);
//根据父节点分组Map<Integer, List<T>>childMap=treeNodeList.stream().collect(Collectors.groupingBy(T::getParentId));
//设置子节点[这里不包括根节点]treeNodeList.forEach(treeNode->treeNode.setChildList(castList(childMap.get(treeNode.getId()),TreeNode.class)));
//将上面已经构建的子树拼到对应的根节点上returnrootNodeList.stream().peek(root->root.setChildList(castList(childMap.get(root.getId()),TreeNode.class))).collect(Collectors.toList());
    }
/*** 这部分不是必要的,childMap.get(treeNode.getId())强转也行*/publicstatic<T>List<T>castList(Objectobj, Class<T>clazz)
    {
List<T>result=newArrayList<T>();
if(objinstanceofList<?>) {
for (Objecto : (List<?>) obj)
            {
result.add(clazz.cast(o));
            }
returnresult;
        }
returnnull;
    }

4、扩展

4.1递归为什么效率低

递归的实现是通过调用函数本身,函数调用的时候,每次调用时要做地址保存,参数传递等,这是通过一个递归工作栈实现的。具体是每次调用函数本身要保存的内容包括:局部变量、形参、调用函数地址、返回值。那么,如果递归调用N次,就要分配N局部变量、N形参、N调用函数地址、N返回值,这势必是影响效率的,同时,这也是内存溢出的原因,因为积累了大量的中间变量无法释放。

4.2 Stream效率问题

在少低数据量的处理场景中(size<=1000),stream 的处理效率是不如传统的 iterator 外部迭代器处理速度快的,但是实际上这些处理任务本身运行时间都低于毫秒,这点效率的差距对普通业务几乎没有影响,反而 stream 可以使得代码更加简洁。

4.3 removeIf和迭代器 remove效率对比

测试1000000份数据,删除一半数据
removeIf() 22ms
iterator.remove 39962ms

相关文章
|
1天前
|
消息中间件 监控 Java
Java 框架界的‘巨星’Spring,让你的代码翩翩起舞!
【6月更文挑战第25天】Spring,Java框架的明星,以其依赖注入(DI)和面向切面编程(AOP)简化开发。DI协调类间协作,AOP提供日志、监控等附加功能。事务管理确保代码稳定性,注解如`@Transactional`自动化事务处理。Spring的集成能力使代码灵活适应多样技术场景,让编程变得优雅高效,犹如舞蹈般流畅。跟随Spring,让代码起舞!
|
2天前
|
JavaScript 前端开发 Java
java 执行 javascript 代码
java 执行 javascript 代码
13 6
|
1天前
|
Java
Java树状结构数据构建(基于hutool)
Java树状结构数据构建(基于hutool)
10 2
|
1天前
|
安全 IDE Java
使用MapStruct和Lombok简化代码
使用MapStruct和Lombok简化代码
14 2
|
2天前
|
存储 自然语言处理 算法
JAVA代码编写规范
JAVA代码编写规范
21 1
|
2天前
|
Java
Java代码的执行顺序
Java代码的执行顺序
|
1天前
|
SQL Java 数据库连接
Java持久化革命:Hibernate与JPA的实战应用
【6月更文挑战第25天】Java世界中,JPA作为ORM规范简化了数据库交互,而Hibernate是其实现者。通过引入依赖、定义注解实体类、配置 Hibernate,开发者能便捷地进行数据操作。Hibernate的使用减少了手动SQL的需求,提升了开发和维护效率,展示了其在持久化技术中的革命性影响和价值。
|
1天前
|
Java 数据库 Spring
解锁 Spring 框架新姿势,让你的 Java 代码更加优雅!
【6月更文挑战第25天】在Spring框架中优化依赖注入、应用AOP和事务管理能提升代码优雅性。例如,使用构造函数注入降低耦合,如将手动创建改为容器管理;通过AOP实现横切关注点,如统一日志记录,避免重复代码;利用`@Transactional`注解简化事务处理,确保数据一致性。这些技巧让代码更简洁、可维护。
|
2天前
|
人工智能 算法 Java
java中经典算法代码整理
java中经典算法代码整理
15 0
|
2天前
|
Java
看看写的多漂亮的java代码
看看写的多漂亮的java代码