场景:有一个无限层级的树结构,最场景的就是系统菜单,通过 pid 关联上级,如何将扁平化的数据列表构建成一个树结构?
有两种方式:
- 使用递归:这种较麻烦
-
不使用递归方式
思路:
将扁平数据使用一个单层循环转换成 TreeNode 对象,并使用 map 结构存储
- 使用一个单层 for 循环遍历扁平的数据列表,配合 map TreeNode 直接构建树结构
下面是具体代码
package crm.mrcode.tree;import com.alibaba.fastjson.JSON;import org.junit.jupiter.api.Test;import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors;import cn.hutool.core.util.RandomUtil;/*** @author mrcode* @date 2021/11/26 20:48*/public class TreeDemo {@Testpublic void buildTreeTest() {// 构建模拟菜单数据List<DataItem> datas = buildDatas(5, 10);// ========== 构建树逻辑// 转成 树节点final Map<Integer, TreeNode> maps = datas.stream().map(item -> new TreeNode(item.getId())).collect(Collectors.toMap(TreeNode::getId, Function.identity()));// 循环构建树List<TreeNode> root = new ArrayList<>();for (DataItem data : datas) {// 不是根节点,直接挂到父节点上面if (data.getPid() != -1) {final TreeNode parentNode = maps.get(data.getPid());List<TreeNode> nodes = parentNode.getNodes();if (nodes == null) {nodes = new ArrayList<>();parentNode.setNodes(nodes);}nodes.add(maps.get(data.getId()));} else {// 是根节点,不用处理,直接添加到 root 列表中root.add(maps.get(data.getId()));}}System.out.println(JSON.toJSON(root));}/*** 构建测试数据** @param rootMax 构建的根节点数据* @param levelMax 构建的树深度* @return*/private List<DataItem> buildDatas(int rootMax, int levelMax) {List<DataItem> datas = new ArrayList<>();for (int i = 0; i < rootMax; i++) {datas.add(new DataItem(i, -1, i));if (RandomUtil.randomBoolean()) {// 构建此节点深度int currentMax = RandomUtil.randomInt(0, levelMax);while (currentMax > 0) {currentMax--;for (int j = 0; j < currentMax; j++) {final DataItem parentItem = datas.get(RandomUtil.randomInt(0, datas.size()));datas.add(new DataItem(RandomUtil.randomInt(100, 9999999), parentItem.getId(), RandomUtil.randomInt(rootMax, rootMax * 2)));}}}}// 返回数据前,按照 sort 排序后返回,这一步可以在数据库中做datas.sort((o1, o2) -> o1.getSort() > o2.getSort() ? 1 : -1);return datas;}/*** 数据库数据*/class DataItem {private Integer id;private Integer pid;private Integer sort; // 数据顺序排序public DataItem(Integer id, Integer pid, Integer sort) {this.id = id;this.pid = pid;this.sort = sort;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Integer getPid() {return pid;}public void setPid(Integer pid) {this.pid = pid;}public Integer getSort() {return sort;}public void setSort(Integer sort) {this.sort = sort;}@Overridepublic String toString() {return "DataItem{" +"id=" + id +", pid=" + pid +", sort=" + sort +'}';}}/*** 树节点*/class TreeNode {private Integer id;private List<TreeNode> nodes;public TreeNode(Integer id) {this.id = id;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public List<TreeNode> getNodes() {return nodes;}public void setNodes(List<TreeNode> nodes) {this.nodes = nodes;}@Overridepublic String toString() {return "TreeNode{" +"id=" + id +", nodes=" + nodes +'}';}}}
:::info 上面的代码其实就是一个简单的固定模板代码,如果你的代码功底好,完全可以使用泛型方式,对象都实现这个接口,然后你的这个构建树里面就面向接口编程就行了。主要的几个字段是:ID、父级 ID,就能完成这个功能了。 :::
使用第三方工具构建
// 构建node列表List<TreeNode<String>> nodeList = CollUtil.newArrayList();nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
// 0表示最顶层的id是0List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");
