这里简单说明一下,这里的树是用户自定义的树,由自己控制父子关系。
在很多应用场景下,用户需要自己去维护一颗树,这颗树一般以列表形式存在数据库里面,如下:
查看树形结构需要由列表生成树,然后再去维护节点关系。
列表生成树的时候,一般的做法是递归查询数据库并组装节点。
这种方法简单,但是一次请求会查询多次数据库,正常来说是不允许这样做的。
为了不多次查询数据库,需要把这个列表一次性查询出来,然后在内存里面把这个列表组装成树,这里有一个问题,如果父节点在前,子节点就直接绑定在父节点,这是没问题的。但是,如果子节点在前,组装的结果就会很实际上的不一样。
直接上代码
TreeMeta
import java.util.List;
/**
* 需要实现这个接口才能构造树
*
* @see com.platform.common.tree.Tree
* @param <K> 一般是String、int、long
* @param <T> vlaue,一般是对象
* @author qiudw
* @since 1.0.0
*/
public interface TreeMeta<K, T> {
/**
* key作为树里面唯一的标识
* @return key
*/
K getKey();
/**
* 父key
* @return key
*/
K getParent();
/**
* 设置子节点
* @param children 子节点列表
*/
void setChildren(List<T> children);
}
ITree
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* 树接口
* @param <K> key标识唯一数节点
* @param <T> key,对应的value
* @author qiudw
* @since 1.0.0
*/
public interface ITree<K, T extends TreeMeta<K, T>> {
/**
* 树转换为List, ArrayList
* @return 列表
*/
List<T> asList();
/**
* 添加节点,使用t.getKey()和getParent()确定关系
* @param t 元素
*/
void add(T t);
/**
* 列表转树,直接转换需要先添加根节点
* @param list 列表
*/
default void addAll(List<T> list) {
list.forEach(this::add);
}
/**
* 删除节点,包括子节点
* @param k 节点key
*/
void delete(K k);
/**
* 根据key查抄节点
* @param k key
* @return value
*/
T find(K k);
/**
* 迭代器
* @return 返回List的迭代器
*/
default Iterator<T> iterator() {
return asList().iterator();
}
/**
* 返回所有的key
* @return key, 为
*/
Set<K> keys();
/**
* 查询父节点,不包括当前节点
* @param k key
* @return 父节点,到根的一级,根节点或者未找到该节点返回空集合
*/
Set<K> findPath(K k);
/**
* 查询所有父节点的key,去重
* @param collection 子节点
* @return 父节点
*/
Set<K> findPath(Collection<K> collection);
/**
* 返回节点数量
* @return 数量
*/
int size();
/**
* 清空树
*/
void empty();
/**
* 是否存在
* @param k key
* @return 存在返回true
*/
boolean exists(K k);
/**
* 生成树,用于多个根节点
* @return 结果
*/
List<T> asListTree();
}
Tree
import lombok.Data;
import lombok.ToString;
import java.util.*;
/**
* 自定义树,多个根节点,有点类似TreeMap
*
* @param <K> key
* @param <T> value
*
* @author qiudw
* @since 1.0.0
*/
@ToString
public class Tree<K, T extends TreeMeta<K, T>> implements ITree<K, T> {
/** 根节点 */
List<Node<K, T>> rootNodes = new ArrayList<>();
/** 节点个数 */
private int size;
/** 用来存放临时的节点,暂时找不到父节点的那种 */
private final List<Node<K, T>> tempNodes = new ArrayList<>();
/** 临时节点集合大小 */
private int tempSize;
/**
* 无参构造方法
*/
public Tree() {}
/**
* 构造方法,解析列表生成树
* @param list 列表
*/
public Tree(List<T> list) {
addAll(list);
}
/**
* 节点
* @param <K> key
* @param <T> value
*/
@Data
static class Node<K, T> {
K k;
T t;
List<Node<K, T>> children;
public Node(K k, T t) {
this.k = k;
this.t = t;
}
Node(K k, T t, List<Node<K, T>> children) {
this.k = k;
this.t = t;
this.children = children;
}
/**
* 添加子节点,记得设置parent
* @param node 节点
*/
void addSubNode(Node<K, T> node) {
if (children == null) {
children = new ArrayList<>();
}
children.add(node);
}
}
@Override
public List<T> asList() {
List<T> result = new LinkedList<>();
// 这里树上的
recurse(result, rootNodes);
// 临时节点也需要
tempNodes.forEach(tempNode -> result.add(tempNode.getT()));
return result;
}
@Override
public void add(T t) {
// key
K key = t.getKey();
// 查询节点
Node<K, T> node = findNode(key);
// 是否能够查询到节点
if (node == null) {
// 节点不存在,查询父节点
K parent = t.getParent();
// 判断父节点ID是否为空
if (parent == null) {
// 父ID为空,直接放在根节点上
rootNodes.add(new Node<>(key, t));
// 临时元素大小+1,为了触发刷新节点
this.tempSize++;
} else {
// 放在临时节点
tempNodes.add(new Node<>(key, t));
}
// 刷新节点,解决子节点先添加的情况
refreshNodes();
// 条数+1
this.size++;
} else {
// 节点存在,重写赋值
node.setK(t.getKey());
node.setT(t);
}
}
@Override
public void delete(K k) {
// 先临时节点去删,如果在临时节点上,删除并结束
if (tempNodes.size() > 0) {
Iterator<Node<K, T>> iterator = tempNodes.iterator();
while (iterator.hasNext()) {
Node<K, T> next = iterator.next();
if (next.getK().equals(k)) {
// 临时节点删除
iterator.remove();
// 条数-1
this.tempSize--;
this.size--;
return;
}
}
}
// 节点删除
delete(rootNodes, k);
// 重写计算大小
this.size = 0;
reloadSize(rootNodes);
// 需要加上临时集合大小
this.size += tempNodes.size();
}
@Override
public T find(K k) {
// 临时节点查找
for (Node<K, T> tempNode : tempNodes) {
if (tempNode.getK().equals(k)) {
return tempNode.getT();
}
}
Node<K, T> node = findNode(k);
if (node != null) {
return node.getT();
}
return null;
}
@Override
public Set<K> keys() {
Set<K> set = new HashSet<>();
getKeys(set, rootNodes);
// 临时节点
tempNodes.forEach(tempNode -> set.add(tempNode.getK()));
return set;
}
@Override
public Set<K> findPath(K k) {
// 集合保存结果
Set<K> keys = new LinkedHashSet<>();
// 当前节点
Node<K, T> currentNode = findNode(k);
if (currentNode == null) {
return keys;
}
// 转成列表
List<T> list = asList();
// 路径查找
findPath(keys, list, currentNode.getT());
return keys;
}
@Override
public Set<K> findPath(Collection<K> collection) {
Set<K> set = new LinkedHashSet<>();
collection.forEach(key -> {
Set<K> path = findPath(key);
if (path != null) {
set.addAll(path);
}
});
return set;
}
@Override
public int size() {
return this.size + tempNodes.size();
}
@Override
public void empty() {
// 大小归零
this.size = 0;
// 根清空
rootNodes.clear();
// 临时节点清空
tempNodes.clear();
}
@Override
public boolean exists(K k) {
for (Node<K, T> tempNode : tempNodes) {
if (tempNode.getK().equals(k)) {
return true;
}
}
Node<K, T> node = findNode(k);
return node != null;
}
@Override
public List<T> asListTree() {
List<T> treeList = new ArrayList<>();
// 生成树
node2Tree(treeList, rootNodes);
// 临时节点
tempNodes.forEach(node -> treeList.add(node.getT()));
return treeList;
}
/**
* node转成树
* @param list 列表
* @param nodeList 节点
*/
private void node2Tree(List<T> list, List<Node<K, T>> nodeList) {
nodeList.forEach(node -> {
T t = node.getT();
list.add(t);
if (node.getChildren() != null && node.getChildren().size() > 0) {
// 子节点
List<T> children = new ArrayList<>();
t.setChildren(children);
node2Tree(children, node.getChildren());
}
});
}
/**
* 查找节点
* @param k key
* @return 为查询到返回null
*/
private Node<K, T> findNode(K k) {
return findNode(rootNodes, k);
}
/**
* 递归查询节点
* @param nodeList 节点列表
* @param k key
* @return 查询到的节点
*/
private Node<K, T> findNode(List<Node<K, T>> nodeList, K k) {
for (Node<K, T> node : nodeList) {
if (node.getK().equals(k)) {
return node;
} else {
List<Node<K, T>> subNodes = node.getChildren();
if (subNodes != null) {
Node<K, T> subNode = findNode(subNodes, k);
if (subNode != null) {
return subNode;
}
}
}
}
return null;
}
/**
* 刷新节点,为了解决"子节点"先添加的情况 <br/>
* 需要考虑子节点原本就在父节点下 <br/>>
* 当前节点不需要与其对应的所有子节点对比 <br/>
*
*/
private void refreshNodes() {
while (tempNodes.size() != tempSize) {
// 临时条数重新赋值
this.tempSize = tempNodes.size();
// 迭代器,需要在遍历的时候删除元素
Iterator<Node<K, T>> iterator = tempNodes.iterator();
while (iterator.hasNext()) {
Node<K, T> node = iterator.next();
// 父节点
Node<K, T> parentNode = findNode(node.getT().getParent());
if (parentNode == null) {
continue;
}
parentNode.addSubNode(node);
iterator.remove();
}
}
}
/**
* 递归生成列表
* @param result 集合,所有的元素都放在这里面
* @param list 节点列表
*/
private void recurse(List<T> result, List<Node<K, T>> list) {
for (Node<K, T> node : list) {
// 加入集合
result.add(node.getT());
if (node.getChildren() != null) {
recurse(result, node.getChildren());
}
}
}
/**
* 删除节点
* @param list 集合
* @param k key
*/
private void delete(List<Node<K, T>> list, K k) {
Iterator<Node<K, T>> iterator = list.iterator();
while (iterator.hasNext()) {
Node<K, T> next = iterator.next();
if (next.getK().equals(k)) {
iterator.remove();
} else {
List<Node<K, T>> children = next.getChildren();
if (children != null) {
delete(children, k);
}
}
}
}
/**
* 重写加载集合大小
* @param list 列表
*/
private void reloadSize(List<Node<K, T>> list) {
for (Node<K, T> node : list) {
this.size++;
List<Node<K, T>> children = node.getChildren();
if (children != null) {
reloadSize(children);
}
}
}
/**
* 生成key集合
* @param keys key集合,结果存储在这里面
* @param list 节点列表
*/
private void getKeys(Set<K> keys, List<Node<K, T>> list) {
for (Node<K, T> node : list) {
keys.add(node.getK());
List<Node<K, T>> children = node.getChildren();
if (children != null) {
getKeys(keys, children);
}
}
}
/**
* 查找
* @param result parentKeys
* @param tempList 待检索的列表
* @param t 当前对象
*/
public void findPath(Set<K> result, List<T> tempList, T t) {
K parent = t.getParent();
if (parent != null) {
for (T t1 : tempList) {
if (t1.getKey().equals(parent)) {
result.add(t1.getKey());
findPath(result, tempList, t1);
}
}
}
}
}
测试
实体类TreeItem
,这里可以随便添加属性
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TreeItem implements TreeMeta<String, TreeItem> {
private String id;
private String parent;
private String name;
private List<TreeItem> children;
public TreeItem(String id, String parent, String name) {
this.id = id;
this.parent = parent;
this.name = name;
}
@Override
public String getKey() {
return id;
}
@Override
public String getParent() {
return parent;
}
}
测试
import java.util.ArrayList;
import java.util.List;
/**
* 树测试
*
* @author qiudw
* @since 1.0.0
*/
public class TreeTest {
public static void main(String[] args) {
List<TreeItem> list = new ArrayList<>();
list.add(new TreeItem("1-1", "1", "二级根节点1-1"));
list.add(new TreeItem("1-2", "1", "二级根节点1-2"));
list.add(new TreeItem("1-2-1", "1-2", "三级根节点1-2-1"));
list.add(new TreeItem("1-2-1", "1-2", "三级根节点1-2-2"));
list.add(new TreeItem("2-1", "2", "二级根节点2-1"));
list.add(new TreeItem("2-2", "2", "二级根节点2-2"));
list.add(new TreeItem("2-2-1", "2-2", "三级根节点2-2-1"));
list.add(new TreeItem("2-2-1", "2-2", "三级根节点2-2-2"));
list.add(new TreeItem("1", null, "一级根节点1"));
list.add(new TreeItem("2", null, "一级根节点2"));
ITree<String, TreeItem> tree = new Tree<>(list);
System.out.println(tree);
List<TreeItem> treeItems = tree.asListTree();
System.out.println(treeItems);
}
}
泛型,为了使用起来方便
- TreeMeta:获取key、获取父节点、设置子节点列表,加入这颗树里面的元素需要实现这个类
- ITree:这是接口,也可以不要
- Tree:这是实现类,也是核心,下面有详细讲解
Node作为Tree的内部类,存储节点以及父子关系
再看几个属性
- rootNodes:根节点列表,这颗树有多个根节点
- size:节点个数
- tempNodes:临时节点,用来存放那些暂时找不到父节点的元素
- tempSize:临时节点集合大小
方法
- List asList():树转换为列表
- void add(T t):添加节点
- void delete(K k):删除节点
- T find(K k):查找节点
- Set keys():返回所有的key
- Set findPath(K k):查询父节点,到根为止的集合
- Set findPath(Collection collection):同上,常用场景,子节点选中的时候,改子节点的父节点也要选中,父节点的父节点需要选中,到根节点为止。
- int size():节点个数
- void empty():清空树
- boolean exists(K k):判断节点是否存在
- List asListTree():转换为目标树形结构
代码里面注释得比较详细了,自己看看应该能理解的。