前言:
项目中有一个部门树的展示需求,组装树结构这个业务场景十分常见,相信每一个从事 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 遍历构建树。
欢迎提出建议及对错误的地方指出纠正。