1、前言

​ 树结构是一种较为常见的数据结构,如功能权限树、企业的组织结构图、行政区划结构图、家族谱、信令消息树等,都表现为树型数据结构。

​ 树结构数据的共性是树节点之间都有相互关系,对于一个节点对象,可以找到父节点、左邻节点、右邻节点、子节点列表。节点本身有其数据类型对象,不同类型的树,不同之处在于节点数据类型不同。

​ 下面针对树型数据,用Java语言给出一种通用树结构数据定义,并提供常规的树节点操作方法。

2、树节点类定义

2.1、基本概念

  • 树节点:即treeNode,树节点是树结构的基本元素,整棵树是由一些列树节点串接而成。每个树节点,其父节点、左邻节点、右邻节点,或者是唯一的,或者为空,(否则成网状结构了)。树节点还包含子节点列表及自身数据对象。
  • 根节点:即rootNode,一棵树的根节点是唯一的,根节点的父节点为空。常见的树型结构数据,看起来好像有一组根节点,如导航栏菜单、菜单栏,实际上那是根节点的一级子节点。为了便于数据库对树型数据的存储,根节点的节点ID规定为0。
  • 叶子节点:即leafNode,叶子节点为树的末梢,叶子节点不包含子节点。
  • 树:使用树节点对象来表示一棵树,由于树节点包含子节点,子节点又包含子子节点。因此一个树节点,就是一棵子树;如果树节点为根节点,则表示整棵树。
  • 父节点:树节点的父节点,当前树节点在父节点的子节点列表中。
  • 子节点:树节点的子节点,在当前节点的子节点列表中。
  • 上级节点:或称祖先节点,从根节点到当前节点的路径上,不含当前节点的所有节点,都是上级节点。
  • 下级节点:或称子孙节点,以当前节点为上级节点的所有节点,都是下级节点。
  • 左邻节点:或称左兄弟节点,或前一节点,与当前节点拥有相同的父节点,且在父节点的子节点列表中,在当前节点的前一个。
  • 右邻节点:或称右兄弟节点,或后一节点,与当前节点拥有相同的父节点,且在父节点的子节点列表中,在当前节点的后一个。
  • 节点数据:每个节点包含一个节点数据对象,不同种类的树节点,其差异就是节点数据对象类型的不同。

2.2、树节点类

​ 树节点类TreeNode,其定义如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import lombok.Data; /**
* @className : TreeNode
* @description : 树节点
* @summary : 节点数据类型,必须实现ITreeNodeData接口类的接口
*
*/
@Data
public class TreeNode<T extends ITreeNodeData> implements Serializable {
private static final long serialVersionUID = 1L; //节点数据对象
private T nodeData; //父节点对象
private TreeNode<T> parent; //子节点列表
private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>(); //是否包含在树中,1表示包含,0表示不包含,此属性为附加属性,在完整树剪枝时使用
private Integer isIncluded = 0;
}

​ 树节点类TreeNode使用泛型T,来表示节点数据类型,规定T必需实现ITreeNodeData接口类,使用接口类而不是基类,是为了不限定节点数据的字段集,且没有多重继承的问题。另外TreeNode也需要实现Serializable接口类,提供节点数据的序列化方法。

​ TreeNode类提供下列属性字段:

  • nodeData字段,节点数据对象,数据类型为泛型T。使用泛型,来达到通用树节点的目的。
  • parent字段,父节点对象,类型仍然是TreeNode。如果父节点为null,表示这是根节点。
  • children字段,子节点列表,其成员仍是TreeNode类型。
  • isIncluded字段,当前节点是否包含在树中,有时候,即使某个节点在树中,通过此属性字段,仍然可以指示该节点是否需要被剪枝。

​ TreeNode类,提供了父节点对象和子节点列表属性字段,从而具有向上搜索和向下搜索的能力。

​ TreeNode类,没有使用左邻节点、右邻节点属性字段,是考虑到兄弟节点的搜索不是很频繁,不用左邻节点、右邻节点属性字段,可以减少节点关系维护的复杂度。同级节点搜索,可以遍历父节点的子节点列表来实现。

3、树节点数据接口类

树节点数据接口类,为ITreeNodeData,其规定了树节点数据对象类型,必需实现的接口方法。代码如下:

package com.abc.questInvest.vo;

/**
* @className : ITreeNodeData
* @description : 树节点数据接口类
*
*/
public interface ITreeNodeData extends Cloneable{
//=============节点基本属性访问接口==============================
//获取节点ID
int getNodeId(); //获取节点名称
String getNodeName(); //获取父节点ID
int getParentId(); //=============Cloneable类接口===================================
//克隆
public Object clone();
}

​ ITreeNodeData类,继承Cloneable,要求树节点数据对象类型必需实现克隆(clone)接口方法。目的是实现对象复制。

​ ITreeNodeData类定义了下列基本的接口方法:

  • getNodeId方法,获取节点ID。
  • getNodeName方法,获取节点名称。
  • getParentId方法,获取父节点ID。
  • clone方法,实现Cloneable接口类需要重载的方法。

4、完整的树节点类

​ 对树节点类TreeNode,进行完善,提供必要的接口。代码如下:

package com.abc.questInvest.vo;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; import lombok.Data; /**
* @className : TreeNode
* @description : 树节点
* @summary : 节点数据类型,必须实现ITreeNodeData接口类的接口
*
*/
@Data
public class TreeNode<T extends ITreeNodeData> implements Serializable {
private static final long serialVersionUID = 1L; //节点数据
private T nodeData; //父节点对象
private TreeNode<T> parent; //子节点
private List<TreeNode<T>> children = new ArrayList<TreeNode<T>>(); //是否包含在树中,1表示包含,0表示不包含,此属性为附加属性,在完整树剪枝时使用
private Integer isIncluded = 0; /**
*
* @methodName : addChildNode
* @description : 添加子节点
* @param childNode : 子节点
*
*/
public void addChildNode(TreeNode<T> childNode) {
childNode.setParent(this);
children.add(childNode);
} /**
*
* @methodName : removeChildNode
* @description : 移除子节点,如果子节点在子节点列表中,则移除,否则无影响
* @param childNode : 子节点
*
*/
public void removeChildNode(TreeNode<T> childNode) {
children.remove(childNode);
} /**
*
* @methodName : clear
* @description : 移除所有子节点
*
*/
public void clear() {
children.clear();
} /**
*
* @methodName : getPrevSibling
* @description : 取得左邻节点
* @return : 如果当前节点为第一个节点,则返回null,否则为前一个节点
*
*/
public TreeNode<T> getPrevSibling(){
if (parent == null) {
//如果为根节点,则返回null
return null;
} List<TreeNode<T>> siblingList = parent.getChildren();
TreeNode<T> node = null;
for (int i = 0; i < siblingList.size(); i++) {
TreeNode<T> item = siblingList.get(i);
if (item == this) {
//找到当前节点
if (i > 0) {
//当前节点不是第一个子节点
//取得前一个节点
node = siblingList.get(i-1);
}
break;
}
}
return node;
} /**
*
* @methodName : getNextSibling
* @description : 取得右邻节点
* @return : 如果当前节点为最后一个节点,则返回null,否则为后一个节点
*
*/
public TreeNode<T> getNextSibling(){
if (parent == null) {
//如果为根节点,则返回null
return null;
} List<TreeNode<T>> siblingList = parent.getChildren();
TreeNode<T> node = null;
for (int i = 0; i < siblingList.size(); i++) {
TreeNode<T> item = siblingList.get(i);
if (item == this) {
//找到当前节点
if (i < siblingList.size()-1) {
//当前节点不是最后一个子节点
//取得后一个节点
node = siblingList.get(i+1);
}
break;
}
}
return node;
} /**
*
* @methodName : lookUpSubNode
* @description : 在当前节点及下级节点中查找指定节点ID的节点
* @param nodeId : 节点ID
* @return : 如果找到,返回对应树节点,否则返回null
*
*/
public TreeNode<T> lookUpSubNode(int nodeId){
TreeNode<T> node = null; //检查当前节点
if (nodeData.getNodeId() == nodeId) {
node = this;
return node;
} //遍历子节点
for(TreeNode<T> item : children) {
node = item.lookUpSubNode(nodeId);
if (node != null) {
//如果节点非空,表示查找到了
break;
}
}
return node;
} /**
*
* @methodName : lookUpSubNode
* @description : 在当前节点及下级节点中查找指定节点名称的节点
* @param nodeName : 节点名称
* @return : 如果找到,返回对应树节点,否则返回null
*
*/
public TreeNode<T> lookUpSubNode(String nodeName){
TreeNode<T> node = null; //检查当前节点
if (nodeData.getNodeName().equals(nodeName)) {
node = this;
return node;
} //遍历子节点
for(TreeNode<T> item : children) {
node = item.lookUpSubNode(nodeName);
if (node != null) {
//如果节点非空,表示查找到了
break;
}
}
return node;
} /**
*
* @methodName : lookUpSuperNode
* @description : 在当前节点及上级节点中查找指定节点ID的节点
* @param nodeId : 节点ID
* @return : 如果找到,返回对应树节点,否则返回null
*
*/
public TreeNode<T> lookUpSuperNode(int nodeId){
TreeNode<T> node = null; //检查当前节点
if (nodeData.getNodeId() == nodeId) {
node = this;
return node;
} //查找父节点
if (parent != null) {
node = parent.lookUpSuperNode(nodeId);
} return node;
} /**
*
* @methodName : lookUpSuperNode
* @description : 在当前节点及上级节点中查找指定节点名称的节点
* @param nodeName : 节点名称
* @return : 如果找到,返回对应树节点,否则返回null
*
*/
public TreeNode<T> lookUpSuperNode(String nodeName){
TreeNode<T> node = null; //检查当前节点
if (nodeData.getNodeName().equals(nodeName)) {
node = this;
return node;
} //查找父节点
if (parent != null) {
node = parent.lookUpSuperNode(nodeName);
} return node;
} /**
*
* @methodName : clone
* @description : 复制树节点,包括所有子节点
* @return : 复制后的树节点
*
*/
@SuppressWarnings("unchecked")
public TreeNode<T> clone(){
//复制当前节点数据信息
TreeNode<T> treeNode = new TreeNode<T>();
//节点数据
treeNode.setNodeData((T)this.nodeData.clone());
//是否包含
treeNode.setIsIncluded(this.isIncluded);
//复制所有子节点
for(TreeNode<T> item : this.children) {
//复制子节点
TreeNode<T> childNode = item.clone();
//加入子节点列表中
treeNode.addChildNode(childNode);
}
return treeNode;
} /**
*
* @methodName : setChildrenIsIncluded
* @description : 设置所有子节点的是否包含属性
* @param isIncluded : 节点是否包含
*
*/
public void setChildrenIsIncluded(Integer isIncluded) {
//遍历所有子节点
for(TreeNode<T> item : this.children) {
item.setIsIncluded(isIncluded);
//子节点的子节点
for(TreeNode<T> subItem : item.getChildren()) {
subItem.setChildrenIsIncluded(isIncluded);
}
}
} /**
*
* @methodName : combineTreeNode
* @description : 将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性位|操作
* @param combineNode : 并入的节点
*
*/
public void combineTreeNode(TreeNode<T> combineNode) {
//当前节点数据的isIncluded属性,使用位|操作
this.setIsIncluded(this.getIsIncluded() | combineNode.getIsIncluded());
//合并子节点
for (int i = 0; i < children.size(); i++) {
TreeNode<T> item = children.get(i);
TreeNode<T> combineItem = combineNode.getChildren().get(i);
//合并子节点
item.combineTreeNode(combineItem);
}
} /**
*
* @methodName : arrange
* @description : 对树进行剪枝处理,即所有isIncluded为0的节点,都被移除
*
*/
public void arrange() {
//遍历子节点列表,如果子节点的isIncluded为0,则剪枝
//倒序遍历
for (int i = children.size() -1; i >=0; i--) {
TreeNode<T> item = children.get(i);
if (item.getIsIncluded() == 0) {
//不包含,需要移除
children.remove(i);
}else {
//包含,当前节点不需要移除,处理其子节点列表
item.arrange();
}
}
} /**
*
* @methodName : getNodeList
* @description : 获取包括自身及所有子节点的列表
* @param nodeList : 树节点列表,入口参数为null
* @return : 树节点列表
*
*/
public List<TreeNode<T>> getNodeList(List<TreeNode<T>> nodeList){ if (nodeList == null) {
//如果入口节点,则参数为null,需要创建
nodeList = new ArrayList<TreeNode<T>>();
} //加入自身节点
nodeList.add(this); //加入所有子节点
for(int i = 0; i < children.size(); i++) {
TreeNode<T> childNode = children.get(i);
childNode.getNodeList(nodeList);
} return nodeList;
} /**
*
* @methodName : loadData
* @description : 将T类型对象的列表加载到树中,调用之前应确保节点的数据对象已创建,
* 且节点ID设置为0
* @param inputList : T类型对象的列表
* @return : 错误的T类型对象的列表
*
*/
public List<T> loadData(List<T> inputList){
//错误的数据对象列表
List<T> errorList = new ArrayList<T>(); //建立节点ID与节点对象的映射表,表示节点加载过程当前已加载的节点集合
Map<Integer,TreeNode<T>> nodeMap = new HashMap<Integer,TreeNode<T>>(); //==================================================================
//要考虑数据次序不一定保证父节点已先加载的情况 //清除数据
clear();
//先加入根节点
nodeMap.put(this.nodeData.getNodeId(), this); //父节点
TreeNode<T> parentNode = null; //遍历inputList,加载树
for(T item : inputList) {
Integer parentId = item.getParentId(); if (nodeMap.containsKey(parentId)) {
//如果父节点已加载,取得父节点对象
parentNode = nodeMap.get(parentId);
//加载树节点
addTreeNode(parentNode,item,nodeMap);
}else {
//如果父节点未加载,则暂时作为游离的独立节点或子树
//加载树节点
addTreeNode(null,item,nodeMap);
}
} //处理游离的节点
for(TreeNode<T> node : nodeMap.values()) {
if (node.getParent() == null && node.getNodeData().getNodeId() != 0) {
//父节点为空,且非根节点
//取得父节点ID
Integer parentId = node.getNodeData().getParentId();
if (nodeMap.containsKey(parentId)) {
//如果父节点存在,,取得父节点对象
parentNode = nodeMap.get(parentId);
//加入父节点中
parentNode.addChildNode(node);
}else {
//parentId对应的节点不存在,说明数据配置错误
errorList.add(node.getNodeData());
}
}
}
return errorList;
} /**
*
* @methodName : addTreeNode
* @description : 加入树节点
* @param parentNode : 父节点
* @param dataInfo : 节点信息对象
* @param nodeMap : 节点ID与节点对象的映射表
*
*/
private void addTreeNode(TreeNode<T> parentNode, T dataInfo,
Map<Integer,TreeNode<T>> nodeMap) {
//生成树节点
TreeNode<T> treeNode = new TreeNode<T>();
//设置节点数据
treeNode.setNodeData((T)dataInfo);
if(parentNode != null) {
//父节点非空,加入父节点中
parentNode.addChildNode(treeNode);
} //加入nodeMap中
nodeMap.put(dataInfo.getNodeId(), treeNode);
} /**
*
* @methodName : toString
* @description : 重载toString方法
* @return : 返回序列化输出的字符串
*
*/
@Override
public String toString() {
String sRet = ""; //根节点的数据部分不必输出
if (parent != null) {
//非根节点
//输出节点开始符号
sRet = "{";
//输出isIncluded值,剪枝后的树,无需输出此字段
//sRet += "\"isIncluded\":" + isIncluded + ",";
//输出当前节点数据
sRet += "\"nodeData\":" + nodeData.toString();
//与前一个节点分隔
sRet += ",";
sRet += "\"children\":";
} //输出子节点
//子节点列表
sRet += "[";
String sChild = "";
//遍历子节点
for(TreeNode<T> item : children) {
//输出子节点数据
if (sChild.equals("")) {
sChild = item.toString();
}else {
sChild += "," + item.toString();
}
}
sRet += sChild;
//结束列表
sRet += "]";
if (parent != null) {
//输出节点结束符号
sRet += "}";
} return sRet;
}
}

TreeNode类提供下列接口方法:

  • addChildNode方法,添加一个子节点。
  • removeChildNode方法,删除一个子节点。
  • clear方法,移除所有子节点。
  • getPrevSibling方法,取得左邻节点。
  • getNextSibling方法,取得右邻节点。
  • lookUpSubNode(int)方法,在当前节点及下级节点中查找指定节点ID的节点。
  • lookUpSubNode(String)方法,在当前节点及下级节点中查找指定节点名称的节点。
  • lookUpSuperNode(int)方法,在当前节点及上级节点中查找指定节点ID的节点。
  • lookUpSuperNode(String)方法,在当前节点及上级节点中查找指定节点名称的节点。
  • clone方法,复制当前树节点表示的树或子树。
  • setChildrenIsIncluded方法,设置全部下级节点的isIncluded属性值。
  • combineTreeNode方法,将结构完全相同的节点合并到本节点中,合并后的节点的isIncluded属性作位或运算。两棵树合并,用完全相同结构的树合并要比不同结构的树合并要方便很多,如多种角色组合的权限树,先用全树合并,然后再剪枝,会方便很多。
  • arrange方法,对树进行剪枝处理,即所有isIncluded为0的节点,都被移除。
  • getNodeList方法,将所有节点对象(包含自身节点及所有下级节点),输出到列表中,便于外部进行遍历访问。由于树的遍历,需要递归,外部不好访问。
  • loadData方法,将T类型对象的列表数据加载到树中。此方法要求外部先设置根节点的节点ID为0,结果实现树的构建,并输出未正确配置的数据对象列表。
  • toString方法,实现Serializable接口类需要重载的方法,提供树结构数据的序列化输出。

5、树结构的节点数据类示例

​ 树结构的节点数据,以权限管理的功能权限树为例,实体类为FunctionInfo。代码如下:

package com.abc.questInvest.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Id; import com.abc.questInvest.vo.ITreeNodeData; import lombok.Data; /**
* @className : FunctionInfo
* @description : 功能节点信息
*
*/
@Data
public class FunctionInfo implements Serializable,ITreeNodeData {
private static final long serialVersionUID = 1L; //功能ID
@Id
@Column(name = "func_id")
private Integer funcId; //功能名称
@Column(name = "func_name")
private String funcName; //父节点ID
@Column(name = "parent_id")
private Integer parentId; //功能所在层级
@Column(name = "level")
private Byte level; //显示顺序
@Column(name = "order_no")
private Integer orderNo; //访问接口url
@Column(name = "url")
private String url; //dom对象的id
@Column(name = "dom_key")
private String domKey; // ================ 接口重载 ====================== //获取节点ID
@Override
public int getNodeId() {
return funcId;
} //获取节点名称
@Override
public String getNodeName() {
return funcName;
} //获取父节点ID
@Override
public int getParentId() {
return parentId;
} //对象克隆
@Override
public Object clone(){
FunctionInfo obj = null;
try{
obj = (FunctionInfo)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
} @Override
public String toString() {
return "{"
+ "\"funcId\":" + funcId + ","
+ "\"funcName\":\"" + funcName + "\","
+ "\"parentId\":" + parentId + ","
+ "\"level\":" + level + ","
+ "\"orderNo\":" + orderNo + ","
+ "\"url\":\"" + url + "\","
+ "\"domKey\":\"" + domKey + "\""
+ "}";
} }

FunctionInfo类对应数据库的功能树表function_tree,表结构如下:

DROP TABLE IF EXISTS `function_tree`;
CREATE TABLE `function_tree`
(
`func_id` INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID',
`func_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名称',
`parent_id` INT(11) NOT NULL DEFAULT 0 COMMENT '父功能ID',
`level` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '功能所在层级',
`order_no` INT(11) NOT NULL DEFAULT 0 COMMENT '显示顺序',
`url` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '访问接口url',
`dom_key` VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom对象的id', `remark` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注', -- 记录操作信息
`operator_name` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人账号',
`delete_flag` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '记录删除标记,1-已删除',
`create_time` DATETIME(3) NOT NULL DEFAULT NOW(3) COMMENT '创建时间',
`update_time` DATETIME(3) DEFAULT NULL ON UPDATE NOW(3) COMMENT '更新时间',
PRIMARY KEY (`func_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='功能表';

6、功能树数据服务

6.1、Dao类

​ Dao类为FunctionTreeDao。代码如下:

package com.abc.questInvest.dao;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select; import com.abc.questInvest.entity.FunctionInfo; /**
* @className : FunctionTreeDao
* @description : function_tree表数据访问类
*
*/
@Mapper
public interface FunctionTreeDao { //查询所有功能树表记录,按parent_id,order_no排序
@Select("SELECT func_id,func_name,parent_id,level,order_no,url,dom_key"
+ " FROM function_tree ORDER BY parent_id,order_no")
List<FunctionInfo> selectAll();
}

​ 注意,查询数据需要按parent_id,order_no排序,有助于提高加载速度。

6.2、Service类

​ Service类为FunctionTreeService。代码如下:

package com.abc.questInvest.service;

import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.vo.TreeNode; /**
* @className : FunctionTreeService
* @description : 功能树服务
*
*/
public interface FunctionTreeService { /**
*
* @methodName : loadData
* @description : 加载数据库中数据
* @return : 成功返回true,否则返回false。
*
*/
public boolean loadData(); /**
*
* @methodName : getFunctionTree
* @description : 获取整个功能树
* @return : 完整的功能树
*
*/
public TreeNode<FunctionInfo> getFunctionTree();
}

6.3、ServiceImpl类

​ Service实现类为FunctionTreeServiceImpl。代码如下:

package com.abc.questInvest.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import com.abc.questInvest.dao.FunctionTreeDao;
import com.abc.questInvest.entity.FunctionInfo;
import com.abc.questInvest.service.FunctionTreeService;
import com.abc.questInvest.vo.TreeNode; import lombok.extern.slf4j.Slf4j; /**
* @className : FunctionTreeServiceImpl
* @description : FunctionTreeService实现类
*
*/
@Slf4j
@Service
public class FunctionTreeServiceImpl implements FunctionTreeService { //function_tree表数据访问对象
@Autowired
private FunctionTreeDao functionTreeDao; //功能树对象
private TreeNode<FunctionInfo> functionTree = new TreeNode<FunctionInfo>(); /**
*
* @methodName : loadData
* @description : 加载数据库中数据
* @return : 成功返回true,否则返回false。
*
*/
@Override
public boolean loadData() { try {
//查询function_tree表,获取全部数据
List<FunctionInfo> functionInfoList = functionTreeDao.selectAll(); //加锁保护,防止脏读
synchronized(functionTree) {
//设置根节点
setRootNode(functionTree);
List<FunctionInfo> errorList = functionTree.loadData(functionInfoList);
if (errorList.size() > 0) {
//有错误信息
//写日志
for(FunctionInfo item : errorList) {
log.error("FunctionTree error with item : " + item.toString());
}
//此时,functionTree是剔除了异常数据的功能树
//返回true或false,视业务需求而定
return false;
}
} }catch(Exception e) {
log.error(e.getMessage());
e.printStackTrace();
return false;
} return true;
} /**
*
* @methodName : getFunctionTree
* @description : 获取整个功能树
* @return : 完整的功能树
*
*/
@Override
public TreeNode<FunctionInfo> getFunctionTree(){
return functionTree;
} /**
*
* @methodName : setRootNode
* @description : 设置根节点
* @param node : 输入的功能树根节点
*
*/
private void setRootNode(TreeNode<FunctionInfo> node) {
node.setParent(null);
//创建空节点数据
node.setNodeData(new FunctionInfo());
//约定根节点的节点ID为0
node.getNodeData().setFuncId(0);
node.getNodeData().setFuncName("root");
//根节点总是包含的
node.setIsIncluded(1);
}
}

​### 6.4、单元测试

​ 对FunctionTreeService使用单元测试,观察效果。代码如下:

/**
* @className : QuestInvestApplicationTest
* @description : 启动测试类
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class QuestInvestApplicationTest {
@Autowired
ServletContext servletContext; @Autowired
FunctionTreeService functionTreeService; @Test
public void functionTreeServiceTest() {
boolean bRet = false;
bRet = functionTreeService.loadData();
if (bRet) {
TreeNode<FunctionInfo> functionTree = functionTreeService.getFunctionTree();
System.out.println(functionTree);
}
}
}

执行测试代码,可以看到输出的功能树数据,将之用网上的JSON查看器查看,可以看到下图的树型结构:

Java通用树结构数据管理的更多相关文章

  1. 【转】java通用URL接口地址调用方式GET和POST方式

    java通用URL接口地址调用方式GET和POST方式,包括建立请求和设置请求头部信息等等......... import java.io.ByteArrayOutputStream; import ...

  2. java通用抹去魔,在边界行动,擦除补偿

    java通用抹去魔 package org.rui.generics.erasure; public class HasF { public void f(){ System.out.println( ...

  3. php与java通用AES加密解密算法

    AES指高级加密标准(Advanced Encryption Standard),是当前最流行的一种密码算法,在web应用开发,特别是对外提供接口时经常会用到,下面是我整理的一套php与java通用的 ...

  4. Ubuntu 14.04下 Java通用安装方法

    参考: 解决Floodlight1.2+Mininet问题及使用安装 Ubuntu下安装JDK1.7图文详解 Ubuntu 14.04下 Java通用安装方法 1.到oracle官网下下载对应jdk包 ...

  5. JAVA 转换 树结构数据

    JAVA 转换 树结构数据 第一步:引入fastjson <dependency> <groupId>com.alibaba</groupId> <artif ...

  6. Java通用oracle和mysql数据库连接

    Java中oracle数据库连接写一个通用类UBUtil(){} import java.io.InputStream; import java.sql.*; import java.util.Pro ...

  7. java 通用对象排序

    一个排序类,一个排序util? no.no.no…… 使用反射机制,写了一个通用的对象排序util,欢迎指正. 实体类: package entity; public class BaseTypeEn ...

  8. JAVA通用BaseServlet的产生和代码实现

    BaseServlet的作用: 我们先写一个工具类:BaseServlet. 我们知道,写一个项目可能会出现N多个Servlet,而且一般一个Servlet只有一个方法(doGet或doPost),如 ...

  9. DateTimeUtil 工具类,android 和 java 通用

    import java.sql.Date;import java.text.SimpleDateFormat; public class DateTimeUtil { public final cla ...

随机推荐

  1. Vulkan移植GPUImage(五)从P到Z的滤镜

    现aoce_vulkan_extra把GPUImage里从P到Z的大部分滤镜用vulkan的ComputeShader实现了,也就是最后一部分的移植,整个过程相对前面来说比较简单,大部分我都是直接复制 ...

  2. mxgraph中mxStencil使用教程

    目录 标签嵌套关系 Shapes shape connections background foreground 其他样式 图形内部颜色绘制 封闭线段绘制 设置一条线的颜色大小 样例 官方文档:htt ...

  3. [re模块、json&pickle模块]

    [re模块.json&pickle模块] re模块 什么是正则? 正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法.或者说:正则就是用来描述一类事物的规则 ...

  4. 在?开源社区版的 AirTag 请收下——GitHub 热点速览 v.21.21

    作者:HelloGitHub-小鱼干 在比特币跌到怀疑人生的时候,看着"出血不止"的荷包,是时候来"薅"一波羊毛了.openhaystack 能让你免去购买 A ...

  5. Mybatis-plus在原有的select查询语句中动态追加查询条件

    一.适用场景 1.使用了xml形式的mapper.2.不想在select查询中大量使用<if>标签来判断条件是否存在而加入条件. 二.步骤 1.自定义wrapper继承QueryWrapp ...

  6. 二进制部署K8S-2集群部署

    二进制部署K8S-2集群部署 感谢老男孩教育王导的公开视频,文档整理自https://www.yuque.com/duduniao/k8s. 因为在后期运行容器需要有大量的物理硬件资源使用的环境是用的 ...

  7. vimdiff env.txt export.txt set.txt

    1. 环境变量 简单理解了变量的概念,就很容易理解环境变量了.环境变量的作用域比自定义变量的要大,如 Shell 的环境变量作用于自身和它的子进程.在所有的 UNIX 和类 UNIX 系统中,每个进程 ...

  8. CENTOS 7 下配置默认网关

    1. ip route 显示和设定路由 1.1 显示路由表 [root@linux-node1 ~]# ip route show default via 192.168.56.2 dev eth0 ...

  9. echart实例

    https://www.makeapie.com/explore.html#sort=rank~timeframe=all~author=all

  10. java面试一日一题:java中的垃圾回收器

    问题:请讲下java中垃圾回收器有哪些? 分析:该问题主要考察hotspot虚拟机下实现的垃圾回收器 回答要点: 主要从以下几点去考虑, 1.垃圾回收器的种类 2.每种垃圾回收器的着重点是什么 前边的 ...