Java 中树结构的组装分享

前言:

项目中有一个部门树的展示需求,组装树结构这个业务场景十分常见,相信每一个从事 Java 开发的同行几乎都遇到过,本篇简单分享一下树结构的组装。

树结构数据模型

数据模型示例如下:

@Data
public class DepartmentPersonnelTreeVO {

    @ApiModelProperty("部门名称")
    private String deptName;

    @ApiModelProperty("部门ID")
    private String deptId;

    @ApiModelProperty("父级部门ID")
    private String parentId;

    @ApiModelProperty("子部门节点")
    private List<DepartmentPersonnelTreeVO> children = new ArrayList<>();

}

树结构数据模型分析

树结构一般有三个必备字段,如下:

  • deptId:示例中描述为部门 id,这是树结构的一个必备字段,一般是当前节点 id,同时也是当前节点的子节点的 parentId。
  • parentId:示例中描述为父级部门 id,很好理解也是树结构中的一个必备字段,构造父子关系使用。
  • children:示例中描述为子部门节点,通用的说发就是当前节点的子节点,也是一个必备字段。

树结构组装,其实就是从数据库取出一组数据,去找到他们之间的父子关系,通畅都会需要遍历处理,以下我们分享递归、双层 for 循环、map 遍历构建树结构。

递归

递归就是从顶级节点一直往下查询,期间需要不停地查询数据库,不推荐递归来组装树结构,这里也就不做演示了。

双层 for 循环

外层每循环一次,就需要内层循环全部数据,去跟外层对比,是否有外层当前循环到的节点的子节点,代码如下:

rivate List<DepartmentPersonnelTreeVO> forBuildTree(List<DepartmentPersonnelTreeVO> departmentPersonnelList) {
        // 结果集
        List<DepartmentPersonnelTreeVO> departmentPersonnelTree = new ArrayList<>();
        for (DepartmentPersonnelTreeVO departmentPersonnelTreeVO : departmentPersonnelList) {
            if ("0".equals(departmentPersonnelTreeVO.getParentId())) {
                //顶级节点
                departmentPersonnelTree.add(departmentPersonnelTreeVO);
            }
            for (DepartmentPersonnelTreeVO personnelTreeVO : departmentPersonnelList) {
                if (!"0".equals(personnelTreeVO.getParentId())
                        && personnelTreeVO.getParentId().equals(departmentPersonnelTreeVO.getDeptId())) {
                    //当前节点不是顶级节点 且当前节点的 父id 和外层循环节点的id 一样 加入外层循环的节点的 子节点中
                    List<DepartmentPersonnelTreeVO> children = departmentPersonnelTreeVO.getChildren();
                    if (ObjectUtil.isEmpty(children)) {
                        children = new ArrayList<>();
                    }
                    children.add(personnelTreeVO);
                    departmentPersonnelTreeVO.setChildren(children);
                }
            }
        }
        return departmentPersonnelTree;
    }

map 遍历处理

map 遍历的原理是先将所有数据放进map 暂存,key 为节点id,value 为节点信息,然后再循环一次 map 就可以得到全部数据,代码如下:

private List<DepartmentPersonnelTreeVO> mapBuildTree(List<DepartmentPersonnelTreeVO> departmentPersonnelList) {
        // 结果集
        List<DepartmentPersonnelTreeVO> departmentPersonnelTree = new ArrayList<>();
        // 用来存储子节点
        Map<String, DepartmentPersonnelTreeVO> childMap = new HashMap<>();
        for (DepartmentPersonnelTreeVO departmentPersonnelTreeVO : departmentPersonnelList) {
            childMap.put(departmentPersonnelTreeVO.getDeptId(), departmentPersonnelTreeVO);
        }
        for (Map.Entry<String, DepartmentPersonnelTreeVO> entry : childMap.entrySet()) {
            DepartmentPersonnelTreeVO departmentPersonnelTreeVO = entry.getValue();
            String parentId = departmentPersonnelTreeVO.getParentId();
            if ("0".equals(parentId)) {
                departmentPersonnelTree.add(departmentPersonnelTreeVO);
            } else {
                //当前节点的父节点
                DepartmentPersonnelTreeVO parentDepartmentPersonnelTreeVO = childMap.get(parentId);
                if (ObjectUtil.isNull(parentDepartmentPersonnelTreeVO)) {
                    log.error("部门名称:{},部门id:{},所属父部门已经不存在", departmentPersonnelTreeVO.getDeptName(), departmentPersonnelTreeVO.getDeptId());
                    continue;
                }
                //把当前节点加入到当前节点父节点的子节点中
                List<DepartmentPersonnelTreeVO> children = parentDepartmentPersonnelTreeVO.getChildren();
                if (parentDepartmentPersonnelTreeVO.getChildren() == null) {
                    children = new ArrayList<>();
                }
                children.add(departmentPersonnelTreeVO);
                parentDepartmentPersonnelTreeVO.setChildren(children);
            }
        }
        return departmentPersonnelTree;
    }

双层 for 循环构建树结构和map 遍历处理构建树结构比较

1500条数据测试结果如下:

 @Override
    public List<DepartmentPersonnelTreeVO> queryDepartmentPersonnelTree() {
        List<DepartmentPersonnelTreeVO> departmentPersonnelList = homePageConfigMapper.queryDepartmentPersonnel();
        long forStart = System.currentTimeMillis();
        forBuildTree(departmentPersonnelList);
        long forTime = System.currentTimeMillis() - forStart;
        System.out.println("for 循环构建树消耗时间:"+ forTime);
        long mapStart = System.currentTimeMillis();
        mapBuildTree(departmentPersonnelList);
        long mapTime = System.currentTimeMillis() - mapStart;
        System.out.println("map 方式构建树消耗时间:"+ mapTime);
        return mapBuildTree(departmentPersonnelList);
    }

测试结果:

for 循环构建树消耗时间:585
map 方式构建树消耗时间:2

很明显双层 for 循环构建树的效率远低于 map 遍历构建树,因此我们推荐使用 map 遍历的方式来构建树结构,而且从代码层面来看,map 遍历方式的代码也比 for 循环看起来更简洁,因此建议使用 map 遍历构建树。

欢迎提出建议及对错误的地方指出纠正。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值