代码实战之道——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技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
65 11
|
6天前
|
SQL Java 数据库连接
如何在 Java 代码中使用 JSqlParser 解析复杂的 SQL 语句?
大家好,我是 V 哥。JSqlParser 是一个用于解析 SQL 语句的 Java 库,可将 SQL 解析为 Java 对象树,支持多种 SQL 类型(如 `SELECT`、`INSERT` 等)。它适用于 SQL 分析、修改、生成和验证等场景。通过 Maven 或 Gradle 安装后,可以方便地在 Java 代码中使用。
94 11
|
10天前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
27天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
54 3
|
1月前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
1月前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
66 2
|
1月前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
90 5
|
1月前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
72 5
|
1月前
|
Java 程序员
Java基础却常被忽略:全面讲解this的实战技巧!
小米,29岁程序员,分享Java中`this`关键字的用法。`this`代表当前对象引用,用于区分成员变量与局部变量、构造方法间调用、支持链式调用及作为参数传递。文章还探讨了`this`在静态方法和匿名内部类中的使用误区,并提供了练习题。
46 1
|
2月前
|
Java API 开发者
Java中的Lambda表达式:简洁代码的利器####
本文探讨了Java中Lambda表达式的概念、用途及其在简化代码和提高开发效率方面的显著作用。通过具体实例,展示了Lambda表达式如何在Java 8及更高版本中替代传统的匿名内部类,使代码更加简洁易读。文章还简要介绍了Lambda表达式的语法和常见用法,帮助开发者更好地理解和应用这一强大的工具。 ####