代码实战之道——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

相关文章
|
2月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
280 8
|
2月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
371 5
|
2月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
2月前
|
安全 Java 开发者
告别NullPointerException:Java Optional实战指南
告别NullPointerException:Java Optional实战指南
263 119
|
2月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
262 115
|
2月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
186 98
|
2月前
|
Java 编译器 API
java最新版和java8的区别,用代码展示
java最新版和java8的区别,用代码展示
277 43
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
Java与生成式AI:构建内容生成与创意辅助系统
生成式AI正在重塑内容创作、软件开发和创意设计的方式。本文深入探讨如何在Java生态中构建支持文本、图像、代码等多种生成任务的创意辅助系统。我们将完整展示集成大型生成模型(如GPT、Stable Diffusion)、处理生成任务队列、优化生成结果以及构建企业级生成式AI应用的全流程,为Java开发者提供构建下一代创意辅助系统的完整技术方案。
222 10
|
2月前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
269 3
|
2月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
268 0