树形工具类
树形结构
1、根据父子结构生成Tree数据
tree_node 表结构
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| icon | varchar(50) | YES | | NULL | |
| name | varchar(50) | YES | | NULL | |
| parent_id | int(11) | NO | | NULL | |
+-----------+-------------+------+-----+---------+-------+
表数据
+----------+------+-------+-----------+
| id | icon | name | parent_id |
+----------+------+-------+-----------+
| 1 | NULL | 根1 | 0 |
| 2 | NULL | 根2 | 0 |
| 3 | NULL | 根3 | 0 |
| 10001 | NULL | 子11 | 1 |
| 10002 | NULL | 子12 | 1 |
| 20001 | NULL | 子21 | 2 |
| 10001001 | NULL | 子111 | 10001 |
| 10001002 | NULL | 子112 | 10001 |
| 20001001 | NULL | 子211 | 20001 |
+----------+------+-------+-----------+
springboot连接
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springbootdemo
username: root
password: 123456
Dao层
public interface TreeNodeRepository extends JpaRepository<TreeNode, Integer> {
}
controller层
@RequestMapping("/getTreeNode")
public List<Tree> getTreeNode() {
List<TreeNode> nodes = repository.findAll();
List<Tree> treeNodes = new ArrayList<>();
nodes.forEach(node -> {
treeNodes.add(node);
});
return TreeUtil.getTree(treeNodes, 0);
}
Tree接口
public interface Tree {
/** 当前节点id*/
Integer id = 0;
/** 父节点id*/
Integer parentId = 0;
/** 子节点集合*/
List<Tree> childrens = null;
Integer getId();
void setId(Integer id);
Integer getParentId();
void setParentId(Integer id);
List<Tree> getChildrens();
void setChildrens(List<Tree> childrens);
}
TreeNode实现类
@Table(name = "tree_node")
@Entity
@Data
public class TreeNode implements Tree {
/**
* 当前节点id
*/
@Id
@Column(unique = true, length = 20, nullable = false)
private Integer id;
/**
* 父节点
*/
@Column(length = 20, nullable = false)
private Integer parentId;
/**
* 节点图标
*/
@Column(length = 50)
private String icon;
/**
* 节点名称
*/
@Column(length = 50)
private String name;
/**
* 子节点
*/
@Transient
private List<Tree> childrens;
}
TreeUtil工具方法 主要是这个类
public class TreeUtil {
/**
* 生成树型数据
* @param nodes 树的基础数据
* @param rootId 根的唯一标识
* @return
*/
public static final List<Tree> getTree(List<Tree> nodes, Integer rootId) {
List<Tree> treeList = new ArrayList<>();
if (nodes == null || nodes.size() <= 0) {
return treeList;
}
// 根点集合
List<Tree> rootNodes = new ArrayList<>();
// 子节点集合
List<Tree> childNodes = new ArrayList<>();
nodes.forEach(node-> {
Integer parentId = node.getParentId();
// 当父节点为0时 当前节点为根节点
if (rootId.equals(parentId)) {
rootNodes.add(node);
} else {
childNodes.add(node);
}
});
rootNodes.forEach(rootNode -> {
addChilds(rootNode, childNodes);
});
return rootNodes;
}
/**
* 为父节点添加所有的子节点
* 一次性获取父节点的所有子节点(然后遍历子节点,把当前子节点当作父节点,为该节点再添加子节点)
* @param parentNode 当前节点
* @param childNodes 所有的节点
*/
private static void addChilds(Tree parentNode, List<Tree> childNodes) {
List<Tree> childs = getChilds(parentNode, childNodes);
if (childs.size() > 0) {
parentNode.setChildrens(childs);
//为每一个子节点获取所有的自己的子节点
childs.forEach(p_node -> {
addChilds(p_node, childNodes);
});
}
}
/**
* 获取父节点的所有直接子节点
*/
private static List<Tree> getChilds(Tree rootNode, List<Tree> childNodes) {
Integer id = rootNode.getId();
List<Tree> nextNodes = new ArrayList<>();
childNodes.forEach(childNode -> {
Integer parentId = childNode.getParentId();
if (id.equals(parentId)) {
nextNodes.add(childNode);
}
});
return nextNodes;
}
}
2、 根据目录字符串生成Tree结构
结构如下:
String[] paths = {"a/b/c/cc.xml", "a/b/bb.xml", "a/d/dd.xml", "e/e.xml"};
|--a
|--b
|--c
|--cc.xml
|--bb.xml
|-->d
|-->dd.xml
|--e
|-->e.xml
树节点实体
@Data
public class FTPDir {
/**
* 文件名
*/
private String label;
/**
* 文件全路径
*/
private String fullPath;
/**
* 子文件
*/
private List<FTPDir> childrens;
/**
* 类型或者文件(可以使用枚举)
*/
private String type;
}
生成操作
package per.qiao.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Create by IntelliJ Idea 2018.2
* 生成文件结构的树
*
* @author: qyp
* Date: 2019-06-27 10:40
*/
public class TreeDir {
private static final String FILE_TYPE = "file";
private static final String DIR_TYPE = "dir";
public static void main(String[] args) {
Map<String, FTPDir> nodeMap = new HashMap<>();
String[] paths = {"a/b/c/cc.xml", "a/b/bb.xml", "a/d/dd.xml", "e/e.xml"};
loadDirs(paths, nodeMap);
System.out.println(nodeMap);
}
public static void loadDirs(String[] paths, Map<String, FTPDir> exists) {
List<String> existsList = new ArrayList<>();
for (String p : paths) {
int idx = p.lastIndexOf("/");
if (idx > -1) {
String parentPath = p.substring(0, idx);
//加载父节点
loadRoom(parentPath, exists, existsList);
}
//加载当前文件
loadRoom(p, exists, existsList);
}
}
/**
* 加载一条路径下的所有节点
* @param path 路径
* @param nodeMap 节点集(最后的节点收集器)
* @param exists 已经存在的节点路径(用来判断当前节点是否已经被添加过)
*/
public static void loadRoom(String path, Map<String, FTPDir> nodeMap, List<String> exists) {
//当前路径已经存在
if (exists.contains(path)) {
return;
}
// 含文件的路径
if (path.contains(".")) {
int idx = path.lastIndexOf("/");
String parentPath = path.substring(0, idx);
//当前节点的名字
String nodeName = path.substring(idx + 1);
if (exists.contains(parentPath)) {
FTPDir fileNode = new FTPDir();
fileNode.setFullPath(path);
fileNode.setLabel(nodeName);
fileNode.setType(FILE_TYPE);
//获取目标父节点
FTPDir parentNode = getNodeByPath(parentPath, nodeMap);
List<FTPDir> childrens = parentNode.getChildrens();
if (childrens == null) {
childrens = new ArrayList<>();
parentNode.setChildrens(childrens);
}
childrens.add(fileNode);
}
} else { //文件夹路径
String nodeName = null;
FTPDir parentNode = null;
if (path.contains("/")) {
int idx = path.lastIndexOf("/");
String parentPath = path.substring(0, idx);
nodeName = path.substring(idx + 1);
loadRoom(parentPath, nodeMap, exists);
parentNode = getNodeByPath(parentPath, nodeMap);
} else {
nodeName = path;
}
FTPDir dirNode = new FTPDir();
dirNode.setFullPath(path);
dirNode.setLabel(nodeName);
dirNode.setType(DIR_TYPE);
//子节点添加到父节点
if (parentNode != null) {
List<FTPDir> childrens = parentNode.getChildrens();
if (childrens == null) {
childrens = new ArrayList<>();
parentNode.setChildrens(childrens);
}
childrens.add(dirNode);
} else {
nodeMap.put(path, dirNode);
}
//目录节点
exists.add(path);
}
}
/**
* 根据路径获取该路径对应的节点
* @param path 节点的路径(id)
* @param nodeMap 节点集
* @return
*/
private static FTPDir getNodeByPath(String path, Map<String, FTPDir> nodeMap) {
//根节点直接返回,nodeMap中第一层都是根节点
if (nodeMap.containsKey(path)) {
return nodeMap.get(path);
}
String[] ps = path.split("/");
//根节点
FTPDir node = nodeMap.get(ps[0]);
return getDirNode(path, node, ps, 1);
}
/**
* 根据路径获取该路径对应的节点
* @param path 需要查找的节点路径
* @param node 该节点所在的根节点
* @param ps 该节点每一层的名称(不包括第一层,也是就根节点)
* @param idx 当前所在层
* @return
*/
private static FTPDir getDirNode(String path, FTPDir node, String[] ps, int idx) {
List<FTPDir> childrens = node.getChildrens();
if (childrens == null) {
childrens = new ArrayList<>();
node.setChildrens(childrens);
}
for (FTPDir c_node : childrens) {
String name = c_node.getLabel();
//去掉多余的遍历
if (!ps[idx].equals(name)) {
continue;
}
if (path.equals(c_node.getFullPath())) {
return c_node;
}
FTPDir targetNode = getDirNode(path, c_node, ps, idx + 1);
if (targetNode != null) {
return targetNode;
}
}
return null;
}
}
下面贡献第二种写法
class TreeNode {
private String id;
private String name;
private List<TreeNode> childrends;
public TreeNode(String id, String name) {
this.id = id;
this.name = name;
}
public static TreeNode getNode(String id, String name) {
return new TreeNode(id, name);
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<TreeNode> getChildrends() {
return childrends;
}
public void setChildrends(List<TreeNode> childrends) {
this.childrends = childrends;
}
}
public class FileTree {
public static void main(String[] args) {
String[] trees = {"a/b/c/cc.xml", "a/b/bb.xml", "a/d/dd.xml", "e/e.xml"};
List<TreeNode> treeNodes = getTree(trees);
System.out.println(treeNodes);
}
private static List<TreeNode> getTree (String[] trees) {
Map<String, TreeNode> nodeMap = new HashMap<>(16);
String pt = null;
for (String tree : trees) {
String[] split = tree.split("/");
for (String s : split) {
if (pt != null) {
pt += "/" + s;
} else {
pt = s;
}
if (nodeMap.containsKey(pt)) {
continue;
}
loadTree(pt, nodeMap);
}
pt = null;
}
return nodeMap.entrySet().stream()
.filter(me -> !me.getKey().contains("/"))
.map(me -> me.getValue())
.collect(Collectors.toList());
}
private static void loadTree(String path, Map<String, TreeNode> nodeMap) {
String nodeName = path.substring(path.lastIndexOf("/") + 1);
if (path.contains(".")) {
addChildrens(path, TreeNode.getNode(path, nodeName), nodeMap);
} else {
//目录节点
TreeNode node = TreeNode.getNode(path, nodeName);
if (path.contains("/")) {
//父节点路径
addChildrens(path, node, nodeMap);
}
nodeMap.putIfAbsent(path, node);
}
}
/**
* 在父节点上挂载当前子节点
* @param path 当前节点的路径
* @param currentNode 当前节点
* @param nodeMap 节点集合
*/
private static void addChildrens(String path, TreeNode currentNode, Map<String, TreeNode> nodeMap) {
// 父节点路径
String parentNodePath = path.substring(0, path.lastIndexOf("/"));
// 文件节点
TreeNode treeNode = nodeMap.get(parentNodePath);
List<TreeNode> childrends = treeNode.getChildrends();
if (childrends == null) {
childrends = new ArrayList<>();
treeNode.setChildrends(childrends);
}
childrends.add(currentNode);
}
}