主頁 > 後端開發 > Java通用樹結構資料管理

Java通用樹結構資料管理

2021-06-25 06:09:20 後端開發

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		: 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		: 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		: 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		: 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方法,洗掉一個子節點,
  • getPrevSibling方法,取得左鄰節點,
  • getNextSibling方法,取得右鄰節點,
  • lookUpSubNode方法,在當前節點及下級節點中查找指定節點ID的節點,
  • lookUpSuperNode方法,在當前節點及上級節點中查找指定節點ID的節點,
  • clone方法,復制當前樹節點表示的樹或子樹,
  • setChildrenIsIncluded方法,設定全部下級節點的isIncluded屬性值,
  • combineTreeNode方法,將結構完全相同的節點合并到本節點中,合并后的節點的isIncluded屬性作位或運算,兩棵樹合并,用完全相同結構的樹合并要比不同結構的樹合并要方便很多,如多種角色組合的權限樹,先用全樹合并,然后再剪枝,會方便很多,
  • arrange方法,對樹進行剪枝處理,即所有isIncluded為0的節點,都被移除,
  • getNodeList方法,將所有節點物件(包含自身節點及所有下級節點),輸出到串列中,便于外部進行遍歷訪問,由于樹的遍歷,需要遞回,外部不好訪問,
  • 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排序,且資料中,func_id要比按parent_id的值大,從而可以實作樹結構的正常加載,

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;
	
	/**	
	 * 
	 * @methodName		: loadData
	 * @description		: 加載資料庫中資料 
	 * @return			: 成功回傳true,否則回傳false,
	 *
	 */
	@Override
	public boolean loadData() {
		
		try {
			//查詢function_tree表,獲取全部資料
			List<FunctionInfo> functionInfoList = functionTreeDao.selectAll();
			
			//創建根節點
			functionTree = new TreeNode<FunctionInfo>();
			functionTree.setParent(null);
			//創建空節點資料
			functionTree.setNodeData(new FunctionInfo());
			//約定根節點的節點ID為0
			functionTree.getNodeData().setFuncId(0);
			//根節點總是包含的
			functionTree.setIsIncluded(1);
			
			//將查詢結果放入functionTree物件中,按照樹的結構組織
			//當前節點
			TreeNode<FunctionInfo> treeNode = null;
			//前一個節點的父節點,考慮到同一個父節點下的子節點物件是連續出現的
			//記下前一個節點的父節點,可以減少節點搜索次數
			TreeNode<FunctionInfo> preNodeParent = null;
			for(FunctionInfo item : functionInfoList) {
				//生成樹節點
				treeNode = new TreeNode<FunctionInfo>();
				//設定節點資料
				treeNode.setNodeData(item);
				
				//如果前一節點的父節點不為空
				if (preNodeParent != null) {
					//比較當前父節點ID與前一節點的父節點的節點ID
					if(preNodeParent.getNodeData().getNodeId() == item.getParentId()) {
						//如果相等,表示父節點沒有變化,繼續加入此父節點
						preNodeParent.addChildNode(treeNode);
					}else {
						//如果父節點ID不同,則表示為新的父節點,從根節點向下搜索新的父節點
						preNodeParent = functionTree.lookUpSubNode(item.getParentId());
						if (preNodeParent != null) {
							//如果找到父節點,則加入
							preNodeParent.addChildNode(treeNode);
						}else {
							//指定父節點ID不在已有樹中,資料加載次序錯誤,或資料錯誤
							log.error("FunctionTreeServiceImpl.loadData error with func_id = " + item.getFuncId());
							return false;
						}
					}
				}else {
					//第一個子節點
					functionTree.addChildNode(treeNode);
					preNodeParent = functionTree;
				}				
			}
			
		}catch(Exception e) {
			log.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
		
		return true;
	 }
		
	/**
	 * 
	 * @methodName		: getFunctionTree
	 * @description		: 獲取整個功能樹
	 * @return			: 完整的功能樹
	 *
	 */
	@Override
	public TreeNode<FunctionInfo> getFunctionTree(){
		return functionTree;
	}
	
}

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查看器查看,可以看到下圖的樹型結構:

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288350.html

標籤:Java

上一篇:分布式事務的 6 種解決方案,寫得非常好!

下一篇:Netty 框架學習 —— 引導

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more