场景:有一个无限层级的树结构,最场景的就是系统菜单,通过 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 {
@Test
public 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;
}
@Override
public 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;
}
@Override
public 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是0
List<Tree<String>> treeList = TreeUtil.build(nodeList, "0");