前言
目录在日常开发中是一种非常常见的需求,今天我们来详细讨论下一种实现方案,以及查询思路。
设计思路
目录其实就是一种树形结构,子目录与父目录形成一种父子关系,所以很自然 的想到维护他们这种关系就能实现了。
数据库设计
1 2 3 4 5 6
| create table tree_node ( id bigint not null comment '主键id' primary key, parent bigint null comment '父节点(root节点为0)' )
|
parent
存储父子关系
- 这种设计很直观,也最容易理解,专业术语成为
邻接表
- 唯一的缺点是在做修改、删除的时候比较麻烦,建议在确定结构后不要再修改目录结构
树节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class TreeNode<T> {
private Long id;
private T date;
private Long parent;
private List<TreeNode<T>> child;
public void addChild(TreeNode<T> node) { if (CollectionUtils.isEmpty(this.child)) { this.child = new ArrayList<>(); } this.child.add(node); }
}
|
常见需求
生成树
根据节点列表生成树
- 给列表根据
id
建立索引
- 遍历列表,将自己加入父节点的孩子列表, 形成父子关系
- 建立一个
ROOT
节点作为根节点,将一级目录放在根节点下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public static <T> TreeNode<T> tree(List<TreeNode<T>> nodes) { Map<Long, TreeNode<T>> map = nodes.stream().collect(Collectors.toMap(TreeNode::getId, item -> item)); TreeNode<T> root = new TreeNode<>(); for (TreeNode<T> node : nodes) { if (node.getParent() == null || node.getParent() == 0) { root.addChild(node); } else { Long parent = node.getParent(); map.get(parent).addChild(node); } } return root; }
|
获取树的某个节点
由于是目录设计,这里采用广度优先遍历,优先遍历上层目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public static <T> TreeNode<T> getNode(long id, TreeNode<T> tree) { Queue<TreeNode<T>> queue = new LinkedBlockingQueue<>(); queue.add(tree); while (!queue.isEmpty()) { TreeNode<T> node = queue.poll(); if (Objects.equals(node.getId(), id)) { return node; } if (!CollectionUtils.isEmpty(node.getChild())) { queue.addAll(node.getChild()); } } return null; }
|
获取所有节点
有时候需要获取某个节点下面的全部节点信息,例如在做搜索的时候就有这个需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public static <T> void getAllTreeNode(TreeNode<T> tree, List<TreeNode<T>> list) { list.add(tree); List<TreeNode<T>> child = tree.getChild(); if (!CollectionUtils.isEmpty(child)) { for (TreeNode<T> node : child) { getAllTreeNode(node, list); } } }
|