树形数据结构的用途
- 树形数据结构在很多需求上都有应用,例如,菜单管理,模块管理,以及常见的文件管理,都会用到树形的数据结构,数据从数据库取出来一般是一个list,这时候就需要后端人员把list的数据转成具有树形的阶层关系的数据。
- 而不同的模块又需要编写自己的构造树形数据的逻辑,而代码逻辑实际上是一样的,所以可以对这一部分的代码抽象化,进行一定的公共化,避免重复的构造。
抽象化
- 确定思路,首先树形的机构需要一个基础类,包含基础属性,其中包括节点nodeId,父节点parentId,祖先节点grandId,子集childrenList。示例代码如下:
package com.nchu.weather.tool.tree;
import com.nchu.weather.vo.permission.PermissionResVO;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* @Description: 树形数据结构基础类
* 定义一个接口,如果需要构建一颗树需要实现该基础实体
* 实现接口的需要传入一个泛型参数T ,表示该类实现了该接口
* @Title: S
* @Package com.nchu.weather.tool.tree
* @Author: Liulaolao
* @CreateTime: 2022/4/12 11:47
*/
public class BaseTreeEntity<T extends BaseTreeEntity> {
/**
* 节点id (命名规范,不要因为业务里使用的id这边命名就更改为id)
*/
protected Long nodeId;
/**
* 父级id
*/
protected Long parentId;
/**
* 祖父级id
*/
protected Long grandId;
/**
* 子集合
*/
protected List<T> childrenList;
/**
* 获取子节点
*/
public List<T> getChildrenList(){
return this.childrenList;
}
/**
* 获取当前节点
*/
public Long getNodeId(){
return this.nodeId;
}
/**
* 获取父节点
*/
public Long getParentId(){
return this.parentId;
}
/**
* 获取祖父节点
*/
public Long getGrandId(){
return this.parentId;
}
/**
* 设置子节点
*/
public void putChildrenList(List<T> children){
if (CollectionUtils.isEmpty(this.childrenList)){
this.childrenList = children;
return;
}
this.childrenList=children;
}
}
- 其次是所需要进行构造树形结构的实体类需要继承BaseTreeEntity,并重写几个get方法,覆盖掉父级的属性,比如实体类的id作为nodeId,就要重写getNodeId(),如果parentId叫法一样,就不需要再实体类里建立parentId了,当然最好还是建议都进行重写,有利于代码的阅读。
示例:
package com.nchu.weather.vo.permission;
import com.nchu.weather.tool.tree.BaseTreeEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* @Description: 权限标识展示VO, 用于查询和更新
* @Title: PermissionResVO
* @Package com.nchu.weather.vo.permission
* @Author: Liulaolao
* @CreateTime: 2022/4/12 11:29
*/
@Data
public class PermissionResVO extends BaseTreeEntity {
@ApiModelProperty("主键id")
private Long id;
@ApiModelProperty("权限标识")
private String permission;
@ApiModelProperty("类型00为目录模块,01为按钮权限")
private String type;
@ApiModelProperty("标识名称")
private String name;
@ApiModelProperty("父级id")
private Long parentId;
@ApiModelProperty("共同祖父id")
private Long grandId;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("创建人员")
private String createBy;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新人员")
private String updateBy;
@ApiModelProperty("版本号")
private Integer version;
@ApiModelProperty("子集合")
private List<PermissionResVO> childrenList;
@Override
public List getChildrenList() {
return this.childrenList;
}
@Override
public Long getNodeId() {
return this.id;
}
@Override
public void putChildrenList(List children) {
setChildrenList(children);
}
@Override
public Long getGrandId(){
return this.grandId;
}
}
- 最后是公共的工具类编写。直接上代码吧,注释比较的详细,应该看懂不成问题。
package com.nchu.weather.tool.tree;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* @Description: 树形数据构造工具类
* @Title: TreeUtil
* @Package com.nchu.weather.tool.tree
* @Author: Liulaolao
* @CreateTime: 2022/4/12 14:14
*/
public class TreeUtil {
/**
* 根据具体的根节点构建树
* @param allList 需要构建树的list
* @param grandId 根节点
* @param <V>
* @return
*/
public static <V extends BaseTreeEntity<V>> List<V> buildTreeList(List<V> allList,Long grandId) {
if (CollectionUtils.isEmpty(allList)) {
return null;
}
//1.获取所有根节点
List<V> roots = allList.stream().filter(c -> Objects.equals(c.getNodeId(),grandId)).collect(Collectors.toList());
//2.获取所有非根节点
List<V> others = allList.stream().filter(c -> c.getGrandId() != null).filter(c-> Objects.equals(c.getGrandId(),grandId)).collect(Collectors.toList());
return buildTree(roots, others);
}
/**
* 构造树形数据结构,根节点的父级id为null;
* @param allList 需要处理的数据
* @param <V>
* @return
*/
public static <V extends BaseTreeEntity<V>> List<V> buildTreeList(List<V> allList) {
if (CollectionUtils.isEmpty(allList)) {
return null;
}
//1.获取所有根节点
List<V> roots = allList.stream().filter(c -> c.getParentId() == null).collect(Collectors.toList());
//2.获取所有非根节点
List<V> others = allList.stream().filter(c -> c.getParentId() != null).collect(Collectors.toList());
return buildTree(roots, others);
}
/**
* 便利根节点,循环构建子集合
* @param roots
* @param others
* @param <V>
* @return
*/
private static <V extends BaseTreeEntity<V>> List<V> buildTree(List<V> roots, List<V> others) {
if (CollectionUtils.isEmpty(roots)) {
return null;
}
Map<String, String> map = new ConcurrentHashMap<>();
//从根节点向下遍历
roots.forEach(beanTree -> addChildren(others, beanTree, map));
return roots;
}
/**
* 查询到改节点的子集合的递归方法
* @param others
* @param beanTree
* @param map
* @param <V>
*/
public static <V extends BaseTreeEntity<V>> void addChildren(List<V> others, V beanTree, Map map) {
//定义一个子集集合,最后会调用实体的putChildrenList,给实体的childrenList赋值
List<V> childrenList = new ArrayList<>();
others.stream()
//过滤集合中已经有的节点
.filter(c -> !map.containsKey(c.getNodeId()))
//筛选属于传入的beanTree下的子节点,元素的parentId = beanTree.nodeID
.filter(c -> c.getParentId().equals(beanTree.getNodeId()))
//至此已经从others中过滤出了beanTree下的子节点集合,开始遍历并加入childreList
.forEach(item -> {
//吧该节点放入map
map.put(item.getNodeId(), item.getParentId());
//递归调用addChildren,继续把子节点的子集加入
addChildren(others, item, map);
childrenList.add(item);
});
//调用beanTree的putChildrenList 给该节点赋值
beanTree.putChildrenList(childrenList);
}
}
测试代码结果如下:
package com.nchu.weather;
import com.nchu.weather.convert.PermissionConvert;
import com.nchu.weather.entity.Permission;
import com.nchu.weather.tool.JsonUtil;
import com.nchu.weather.tool.tree.TreeUtil;
import com.nchu.weather.vo.permission.PermissionResVO;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 测试树形数据结构构造
* @Title: TestTree
* @Package com.nchu.weather
* @Author: Liulaolao
* @CreateTime: 2022/4/12 15:56
*/
@Slf4j
public class TestTree {
public static void main(String[] args) {
List<PermissionResVO> permissionResVOList = new ArrayList<>();
PermissionResVO permissionResVO1 = new PermissionResVO();
permissionResVO1.setId(1234l);
permissionResVO1.setName("我是头部:A");
PermissionResVO permissionResVO2 = new PermissionResVO();
permissionResVO2.setId(78372l);
permissionResVO2.setParentId(1234l);
permissionResVO2.setName("我是二级:B");
PermissionResVO permissionResVO3 = new PermissionResVO();
permissionResVO3.setId(78371l);
permissionResVO3.setParentId(1234l);
permissionResVO3.setName("我是二级:C");
PermissionResVO permissionResVO4 = new PermissionResVO();
permissionResVO4.setId(78370l);
permissionResVO4.setParentId(78371l);
permissionResVO4.setName("我是三级:D");
permissionResVOList.add(permissionResVO1);
permissionResVOList.add(permissionResVO2);
permissionResVOList.add(permissionResVO3);
permissionResVOList.add(permissionResVO4);
Permission permission = new Permission();
permission.setId(1l);
PermissionResVO temp = PermissionConvert.INSTANCE.convert(permission);
List<PermissionResVO> treeList = TreeUtil.buildTreeList(permissionResVOList);
log.info("构建的树数据{}", JsonUtil.objToString(treeList));
}
}
结果如下:
17:22:39.664 [main] INFO com.nchu.weather.TestTree - 构建的树数据[{“nodeId”:“1234”,“parentId”:null,“grandId”:null,“childrenList”:[{“nodeId”:“78372”,“parentId”:“1234”,“grandId”:null,“childrenList”:[],“id”:“78372”,“permission”:null,“type”:null,“name”:“我是二级:B”,“createTime”:null,“createBy”:null,“updateTime”:null,“updateBy”:null,“version”:null},{“nodeId”:“78371”,“parentId”:“1234”,“grandId”:null,“childrenList”:[{“nodeId”:“78370”,“parentId”:“78371”,“grandId”:null,“childrenList”:[],“id”:“78370”,“permission”:null,“type”:null,“name”:“我是三级:D”,“createTime”:null,“createBy”:null,“updateTime”:null,“updateBy”:null,“version”:null}],“id”:“78371”,“permission”:null,“type”:null,“name”:“我是二级:C”,“createTime”:null,“createBy”:null,“updateTime”:null,“updateBy”:null,“version”:null}],“id”:“1234”,“permission”:null,“type”:null,“name”:“我是头部:A”,“createTime”:null,“createBy”:null,“updateTime”:null,“updateBy”:null,“version”:null}]