java构建树形结构的方式及如何组装树状结构数据
目录
- 思考思路
- 实现:Java构建树状结构数据
- 1. 递归方式
- 2. 利用Stream流
- 3. 基于Map(推荐)
- 3.1 基于Map方式分析
- 4. 总结
思考思路
我在考虑如何在Java中构建树状数据结构:常见的方法包括递归、父子引用和使用节点列表等。可以通过创建树节点类来表示树形结构,例如,可以定义“树节点”类为:class Node { T data; List<Node> children; Node parent; }
。
我将提供详细分析,看看哪些方法在实际项目中更常见,如何构建包含父子关系和操作树的数据结构。
这个问题的答案可能会包括多个常见的实现方式。
在 Java 中构建树状结构,常见方法包括:1. 递归:从父子关系数据递归构建树;2. 使用 Map/HashMap:存储节点并根据父子关系链接它们。我可以提供代码示例,如定义TreeNode 类如下:
public class TreeNode<T> { private T data; private List<TreeNode<T>> children = new ArrayList<>(); public TreeNode(T data) { this.data = data; } public void addChild(TreeNode<T> child) { children.add(child); } }
实现:Java构建树状结构数据
目的: 在 Java 中构建树状结构数据是一个非常常见的需求,通常用于展示分层数据(如:组织架构、菜单、文件目录等),下面详细介绍几种常见的方法、代码案例及实际项目中的实践经验。比如实现这样的分层效果:
这些数据都在数据库,所以查数据库:查出所有的数据,然后再组装返回给前端解析即可,当然数据表中应该设计成这样:
组装时只需要组装成一个 list 返回即可,前端拿到 list 解析。
1. 递归方式
原理: 递归方法基于“父子关系”的层级遍历,从根节点开始递归查找和添加子节点。适合节点层次较少的场景。
实现: 利用递归的方式进行树状结构数据的组装,代码如下:
@Data @NoArgsConstructor public class DeptEntity { private Long deptId; private Long parentId; private String deptName; private List<DeptEntity> children; public DeptEntity(Long deptId, Long parentId, String deptName) { this.deptId = deptId; this.parentId = parentId; this.deptName = deptName; } }
public class Test { // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据 public static List<DeptEntity> getData() { List<DeptEntity> list = new ArrayList<>(); list.add(new DeptEntity(100L, 0L, "若依编程科技")); list.add(new DeptEntity(101L, 100L, "深圳总公司")); list.add(new DeptEntity(102L, 100L, "长沙分公司")); list.add(new DeptEntity(103L, 101L, "研发部门")); list.add(new DeptEntity(104L, 101L, "市场部门")); list.add(new DeptEntity(105L, 102L, "运维部门")); list.add(new DeptEntity(106L, 102L, "财务部门")); return list; } public static void main(String[] args) { recursionBuildTree(); } // 方法1:递归 public static void recursionBuildTree() { // 获取假数据 List<DeptEntity> data = getData(); // 留住最顶层节点 List<DeptEntity> collect = data.stream() .filter(d -> d.getParentId() == 0L) .collect(Collectors.toList()); //首先设置最顶层子节点 for (DeptEntity deptEntity : collect) { deptEntity.setChildren(recursionBuildChildren(deptEntity.getDeptId(), data)); } System.out.println(collect); } // 设置子节点 public static List<DeptEntity> recursionBuildChildren(Long deptId, List<DeptEntity> data) { // 保存子节点 List<DeptEntity> children = new ArrayList<>(); // 寻找子节点 for (DeptEntity deptEntity : data) { // 因为data是最原始数据,还是需要过滤掉最顶层节点 if (deptEntity.getParentId() == 0L) { continue; } if (deptEntity.getParentId().equals(deptId)) { children.add(deptEntity); } } //递归遍历子节点 for (DeptEntity deptEntity : children) { deptEntity.setChildren(recursionBuildChildren(deptEntity.getDeptId(), data)); } return children; } }
2. 利用Stream流
1、上面的 DeptEntity
类代码不变
2、写方法:
public class Test { // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据 public static List<DeptEntity> getData() { List<DeptEntity> list = new ArrayList<>(); list.add(new DeptEntity(100L, 0L, "若依科技")); list.add(new DeptEntity(101L, 100L, "深圳总公司")); list.add(new DeptEntity(102L, 100L, "长沙分公司")); list.add(new DeptEntity(103L, 101L, "研发部门")); list.add(new DeptEntity(104L, 101L, "市场部门")); list.add(new DeptEntity(105L, 102L, "运维部门")); list.add(new DeptEntity(106L, 102L, "财务部门")); return list; } public static void main(String[] args) { streamBuildTree(); } // 方法2:Stream流 public static void streamBuildTree() { List<DeptEntity> data = getData(); // 按照parentId分组,将数据转为Map<parentId, 属于这个parentId的数据的一个集合> javascript Map<Long, List<DeptEntity>> collect =javascript data.stream() .collect(Collectors.groupingBy(DeptEntity::getParentId)); // 开始构建树 List<DeptEntity> list = streamBuildChildren(0L, collect); System.out.println(list); } // 设置子节点 public static List<DeptEntity> streamBuildChildren(Long parentId, Map<Long, List<DeptEntity>> collect) { return Optional.ofNullable(collect.get(parentId)) .orElse(new ArrayList<>()) // 避免报空指针异常 .stream() .peek(child -> child.setChildren(streamBuildChildren(child.getDeptId(), collect))) .collect(Collectors.toList()); } }
3. 基于Map(推荐)
原理: 首先将所有节点存入一个哈希表(Map),键为节点的 id。然后遍历所有节点,根据其 parentId 找到对应父节点并加入到父节点的子节点列表中。这样可以避免递归的性能问题。
好处: 在 Java 中,使用 Map 来构建树结构是一种高效的方法。该方法避免了递归的性能问题,并且可以快速查找节点,提高构建树的效率。
实现: 如下:
1、上面的 DeptEntity
类代码不变
2、写方法:
public class Test { // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据 public static List<DeptEntity> getData() { List<DeptEntity> list = new ArrayList<>(); list.add(new DeptEntity(100L, 0L, "若依科技")); list.add(new DeptEntity(101L, 100L, "深圳总公司")); list.add(new DeptEntity(102L, 100L, "长沙分公司")); list.add(new DeptEntity(103L, 101L, "研发部门")); list.add(new DeptEntity(104L, 101L, "市场部门")); list.add(new DeptEntity(105L, 102L, "运维部门")); list.add(new DeptEntity(106L, 102L, "财务部门")); return list; } public static void main(String[] args) { mapBuildTree(); } // 方法3:基于Map 1 public static void mapBuildTree() { // 获取假数据 List<DeptEntity> data = getData(); // 存放根节点 List<DeptEntity> roots = new ArrayList<>(); // 存放所有子节点 Map<Long, List<DeptEntity>> childMap = new HashMap<>(); // 1.初始化childMap,确保不会出现null for (DeptEntity deptEntity : data) { childMap.put(deptEntity.getDeptId(), new ArrayList<>()); } // 2.构建树结构 for (DeptEntity deptEntity : data) { // 根节点 if (deptEntity.getParentId() == null || deptEntity.getParentId() == 0) { roots.add(deptEntity); } else { // 非根节点,加入到父节点的children列表 childMap.get(deptEntity.getParentId()).add(deptEntity); } } // 3.统一赋值children for (DeptEntity deptEntity : data) { deptEntity.setChildren(childMap.get(deptEntity.getDeptId())); } System.out.println(roots); } }
基于 Map 的方式还有一个,如下:
public class DeptEntity { private Long deptId; private Long parentId; private String deptName; private List<DeptEntity> children = new ArrayList<>(); // 构造方法 public DeptEntity(Long deptId, Long parentId, String deptName) { this.deptId = deptId; this.parentId = parentId; this.deptName = deptName; } // Getter & Setter public Long getDeptId() { return deptId; } public Long getParentId() { return parentId; } public String getDeptName() { return deptName; } public List<DeptEntity> getChildren() { return children; } // 加一个这个方法 public void addChild(DeptEntity child) { this.children.add(child); } }
public class Test { // 假数据:本来这一步是需要从数据库中查找所有的数据,然后再组装,为了方便,这里弄成假数据 public static List<DeptEntity> getData() { Lipythonst<DeptEntity> list = new ArrayList<>(); list.add(new DeptEntity(100L, 0L, "若依科技")); list.add(new DeptEntity(101L, 100L, "深圳总公司")); list.add(new DeptEntity(102L, 100L, "长沙分公司")); list.add(new DeptEntity(103L, 101L, "研发部门")); list.add(new DeptEntity(104L, 101L, "市场部门")); list.add(new DeptEntity(105L, 102L, "运维部门")); list.add(new DeptEntity(106L, 102L, "财务部门")); return list; } public static void main(String[] args) { mapBuildTree(); } // 方法3:基于Map 2 public static void mapBuildTree() { // 获取假数据 List<DeptEntity> data = getData(); List<DeptEntity> roots = new ArrayList<>(); Map<Long, DeptEntity> childMap = new HashMap<>(); // 1.将所有节点存入Map中 for (DeptEntity deptEntity : data) { childMap.put(deptEntity.getDeptId(), deptEntity); } // 2.构建树结构 for (DeptEntity deptEntity : data) { // 根节点 if (deptEntity.getParentId() == null || deptEntity.getParentId() == 0) { roots.add(deptEntity); } else { // 非根节点,加入到父节点的children列表 DeptEntity parent = map.get(deptEntity.getParentId()); if (parent != null) { parent.addChild(deptEntity); } } } System.out.println(roots); } }
3.1 基于Map方式分析
1、代码分析:
- 先将所有节点存入
Map<Long, DeptEntity>
,便于快速查找。 - 遍历 data:1、如果 parentId 为 null 或 0,表示它是根节点,加入 roots 列表。2、否则,在 map 中查找 parentId 对应的父节点,并将该节点加入父节点的 children 列表。
2、优势分析:
(1)时间复杂度:遍历列表 data 两次,因此时间复杂度为 O(n),相比递归构建树(可能达到 O(n²))效率更高。(2)空间复杂度:使用了 Map<Long, DeptEntity>
,其空间复杂度是 O(n),额外开销较小。
(3)避免递归问题:采用 Map 直接查找父节点,避免递归调用,避免栈溢出问题,特别适用于深度较大的树结构。
3、适合场景:
✅ 数据量大,如百万级别的分类、菜单、组织架构等。
✅ 层级较深,如文件目录、权限管理系统。
✅ 频繁查询,构建后可以缓存树,提高性能。
4、不适合场景:
❌ 数据量特别小(<100),可以直接使用递归方法,代码更简洁。
4. 总结
到此这篇关于java构建树形结构的方式及如何组装树状结构数据的文章就介绍到这了,更多相关java构建树形结构数据内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面www.devze.com的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论