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. unbuntu下清理磁盘空间

    把很多大文件删除,并清空回收站后,发现可用存储空间并没增大,如图: 用find /home -size +500k 过滤出大于500k bytes的文件,发现原来删除的yuv文件都被置于.cache目 ...

  2. 中国排名前100的IC设计公司

    中国排名前100的IC设计公司 北京地区大唐微电子技术有限公司北京北大众志微系统科技有限责任公司北京中星微电子有限公司中国华大集成电路设计中心  北京中电华大电子设计有限责任公司  北京清华同方微电子 ...

  3. 【近取 key】功能规格说明书

    目录 前置信息说明 概念介绍 记忆宫殿 A4纸背单词法 词图 单词掌握程度相关 用户和典型场景 系统功能设计 主页 词图相关功能 创建词图 查看词图 复习词图 个人控制台相关功能 我的词图 统计信息 ...

  4. 25.数据结构,LinkedList ,泛型,类型通配符

    3.数据结构 数据结构是计算机存储,组织数据的方式.是指相互之间存在的一种或多种特定关系的数据元素的集合 通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率 ---------常见的数据结构 ...

  5. [Java] 开课吧--JVM

    双亲委派 向上委托,向下加载 收到加载任务后,先交给父类加载器,只有当父类加载器无法完成,才会执行加载 保证只有一个类加载器加载,避免重复加载 破坏:JDK 1.2后才使用,JDK  1.1的核心类没 ...

  6. 【MySQL】MySQL-front等客户端连接MySQL_8.0等失败的解决办法

    ALTER USER 'root'@'localhost' IDENTIFIED BY '新的密码' PASSWORD EXPIRE NEVER; ALTER USER 'root'@'localho ...

  7. Linux创建RAID5_实战

    Linux创建RAID5实战 Linux创建RAID5 RAID5最少由三个硬盘组成,它将数据分散存储于阵列中的每个硬盘,并且还伴有一个数据校验位,数据位与校验位通过算法能相互验证 RAID5最多能允 ...

  8. 无法开机 如果您的手机黑屏无法开机,可以按照以下方式操作尝试: 如果是,使用原装充电器或使用弱电流方式充电(例如使用电脑USB接口充电)充电15-30分钟后尝试重新开机;注意:电量过低引起的无法开机,刚插入充电器时可能不亮屏但呼吸灯闪烁状态。

    https://www.mi.com/service/support/startup 无法开机 如果您的手机黑屏无法开机,可以按照以下方式操作尝试: 技术支持 如何刷机 无法开机 手机自动关机.重启 ...

  9. Datatables 实现前端分页处理

    引言 Datatables 是一款 jquery 表格插件.它是一个高度灵活的工具,可以将任何 HTML 表格添加高级的交互功能. 支持分页(包括即时搜索和排序) 支持几乎任何数据源(DOM.java ...

  10. C语言编程 菜鸟练习100题(41-50)

    [练习41]字符串翻转 0. 题目: 字符串翻转 1. 分析: 学习递归思想和方法. 2. 程序: 方法一: #include <stdio.h> void reverseSentence ...