场景:有一个无限层级的树结构,最场景的就是系统菜单,通过 pid 关联上级,如何将扁平化的数据列表构建成一个树结构?

有两种方式:

  1. 使用递归:这种较麻烦
  2. 不用递归:这种实现起来简单,两个单层 for 循环搞定

    不使用递归方式

    思路:

  3. 将扁平数据使用一个单层循环转换成 TreeNode 对象,并使用 map 结构存储

  4. 使用一个单层 for 循环遍历扁平的数据列表,配合 map TreeNode 直接构建树结构

下面是具体代码

  1. package crm.mrcode.tree;
  2. import com.alibaba.fastjson.JSON;
  3. import org.junit.jupiter.api.Test;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import java.util.Map;
  7. import java.util.function.Function;
  8. import java.util.stream.Collectors;
  9. import cn.hutool.core.util.RandomUtil;
  10. /**
  11. * @author mrcode
  12. * @date 2021/11/26 20:48
  13. */
  14. public class TreeDemo {
  15. @Test
  16. public void buildTreeTest() {
  17. // 构建模拟菜单数据
  18. List<DataItem> datas = buildDatas(5, 10);
  19. // ========== 构建树逻辑
  20. // 转成 树节点
  21. final Map<Integer, TreeNode> maps = datas.stream().map(item -> new TreeNode(item.getId())).collect(Collectors.toMap(TreeNode::getId, Function.identity()));
  22. // 循环构建树
  23. List<TreeNode> root = new ArrayList<>();
  24. for (DataItem data : datas) {
  25. // 不是根节点,直接挂到父节点上面
  26. if (data.getPid() != -1) {
  27. final TreeNode parentNode = maps.get(data.getPid());
  28. List<TreeNode> nodes = parentNode.getNodes();
  29. if (nodes == null) {
  30. nodes = new ArrayList<>();
  31. parentNode.setNodes(nodes);
  32. }
  33. nodes.add(maps.get(data.getId()));
  34. } else {
  35. // 是根节点,不用处理,直接添加到 root 列表中
  36. root.add(maps.get(data.getId()));
  37. }
  38. }
  39. System.out.println(JSON.toJSON(root));
  40. }
  41. /**
  42. * 构建测试数据
  43. *
  44. * @param rootMax 构建的根节点数据
  45. * @param levelMax 构建的树深度
  46. * @return
  47. */
  48. private List<DataItem> buildDatas(int rootMax, int levelMax) {
  49. List<DataItem> datas = new ArrayList<>();
  50. for (int i = 0; i < rootMax; i++) {
  51. datas.add(new DataItem(i, -1, i));
  52. if (RandomUtil.randomBoolean()) {
  53. // 构建此节点深度
  54. int currentMax = RandomUtil.randomInt(0, levelMax);
  55. while (currentMax > 0) {
  56. currentMax--;
  57. for (int j = 0; j < currentMax; j++) {
  58. final DataItem parentItem = datas.get(RandomUtil.randomInt(0, datas.size()));
  59. datas.add(new DataItem(RandomUtil.randomInt(100, 9999999), parentItem.getId(), RandomUtil.randomInt(rootMax, rootMax * 2)));
  60. }
  61. }
  62. }
  63. }
  64. // 返回数据前,按照 sort 排序后返回,这一步可以在数据库中做
  65. datas.sort((o1, o2) -> o1.getSort() > o2.getSort() ? 1 : -1);
  66. return datas;
  67. }
  68. /**
  69. * 数据库数据
  70. */
  71. class DataItem {
  72. private Integer id;
  73. private Integer pid;
  74. private Integer sort; // 数据顺序排序
  75. public DataItem(Integer id, Integer pid, Integer sort) {
  76. this.id = id;
  77. this.pid = pid;
  78. this.sort = sort;
  79. }
  80. public Integer getId() {
  81. return id;
  82. }
  83. public void setId(Integer id) {
  84. this.id = id;
  85. }
  86. public Integer getPid() {
  87. return pid;
  88. }
  89. public void setPid(Integer pid) {
  90. this.pid = pid;
  91. }
  92. public Integer getSort() {
  93. return sort;
  94. }
  95. public void setSort(Integer sort) {
  96. this.sort = sort;
  97. }
  98. @Override
  99. public String toString() {
  100. return "DataItem{" +
  101. "id=" + id +
  102. ", pid=" + pid +
  103. ", sort=" + sort +
  104. '}';
  105. }
  106. }
  107. /**
  108. * 树节点
  109. */
  110. class TreeNode {
  111. private Integer id;
  112. private List<TreeNode> nodes;
  113. public TreeNode(Integer id) {
  114. this.id = id;
  115. }
  116. public Integer getId() {
  117. return id;
  118. }
  119. public void setId(Integer id) {
  120. this.id = id;
  121. }
  122. public List<TreeNode> getNodes() {
  123. return nodes;
  124. }
  125. public void setNodes(List<TreeNode> nodes) {
  126. this.nodes = nodes;
  127. }
  128. @Override
  129. public String toString() {
  130. return "TreeNode{" +
  131. "id=" + id +
  132. ", nodes=" + nodes +
  133. '}';
  134. }
  135. }
  136. }

:::info 上面的代码其实就是一个简单的固定模板代码,如果你的代码功底好,完全可以使用泛型方式,对象都实现这个接口,然后你的这个构建树里面就面向接口编程就行了。主要的几个字段是:ID、父级 ID,就能完成这个功能了。 :::

使用第三方工具构建

  1. // 构建node列表
  2. List<TreeNode<String>> nodeList = CollUtil.newArrayList();
  3. nodeList.add(new TreeNode<>("1", "0", "系统管理", 5));
  4. nodeList.add(new TreeNode<>("11", "1", "用户管理", 222222));
  5. nodeList.add(new TreeNode<>("111", "11", "用户添加", 0));
  6. nodeList.add(new TreeNode<>("2", "0", "店铺管理", 1));
  7. nodeList.add(new TreeNode<>("21", "2", "商品管理", 44));
  8. nodeList.add(new TreeNode<>("221", "2", "商品管理2", 2));
  1. // 0表示最顶层的id是0
  2. List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");

详细请参考 hutool 的 TreeUtil