1.需求描述
對於數據庫設計中,對於層級結構的設計一般使用parentId對於Id的引用實現。令我們愉悅的是,Oracle還提供了關鍵字PRIOR查詢樹狀結構的語句。下面是對於多級菜單的層級結構查詢的Sql語句。具體語法大家可以自行學習,這裏不對此做過多的解釋。
SELECT
ID,
NVL(TO_CHAR(PARENT_ID), 'NULL') PARENT_ID,
MODULE_NAME,
BS_URL,
ICON,
ORDER_NO,
IS_VISIBLE,
SYS_CONNECT_BY_PATH(MODULE_NAME, '/') PATH
FROM
SM_MODULE
START WITH
PARENT_ID IS NULL
CONNECT BY
PRIOR ID = PARENT_ID
查詢的結果如下圖:
通過上面的結果我們知道,通過此Sql語句查詢到的數據是按先查詢PARENT_ID == NULL(也就是START WITH PARENT_ID IS NULL)的記錄,然後以每一條記錄的ID作爲父級ID執行查詢,對於查詢的結果集合再以次ID作爲父級節點查詢,依次遞歸查詢。所以得到了上面的數據。而對於我們只要知道排在下面數據的父節點肯定在其上面存在(頂級節點除外)足以。
結果集查到了,那麼接下來是如何將其按照層級結構存入到Java對象中。下面我們分別以HashMap和棧的方式實現需求,通過比較,你會深深體會到基於棧實現此需求是多麼的香。
2.數據準備
爲了便於測試研究,也爲了照顧平時不怎麼使用Oracle的夥伴,我們並沒有直接連接Oracle查詢數據,而是模擬通過Oracle獲得的結果數據。
2.1 Model對象實現
package cn.surpass.jdk8newfuture.self.tree;
import java.util.ArrayList;
import java.util.List;
/**
* @Description
* @Author SurpassLiang
* @Version V1.0.0
* @Since 1.0
* @Date 2020/4/11
*/
public class Model {
private String id;
private String parentId;
private List<Model> modelList= new ArrayList<>();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public Model(String id, String parentId) {
this.id = id;
this.parentId = parentId;
}
@Override
public String toString() {
return "Model{" +
"id='" + id + '\'' +
", parentId='" + parentId + '\'' +
", modelList=" + modelList +
'}';
}
public List<Model> getModelList() {
return modelList;
}
public void setModelList(List<Model> modelList) {
this.modelList = modelList;
}
}
2.2 測試數據
我們使用Junit測試工具測試,所以我們初始化數據有如下代碼:
package cn.surpass.jdk8newfuture.self.tree;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
/**
* @Description
* @Author SurpassLiang
* @Version V1.0.0
* @Since 1.0
* @Date 2020/4/11
*/
public class TreeTest {
List<Model> modelList =new ArrayList<>();
@Before
public void testTree(){
modelList.add(new Model("1",null));
modelList.add(new Model("2","1"));
modelList.add(new Model("3","1"));
modelList.add(new Model("4","3"));
modelList.add(new Model("5","1"));
modelList.add(new Model("6",null));
modelList.add(new Model("7","6"));
modelList.add(new Model("8","6"));
modelList.add(new Model("9","6"));
modelList.add(new Model("10","9"));
modelList.add(new Model("11","9"));
modelList.add(new Model("12","9"));
modelList.add(new Model("13","12"));
}
}
3.基於HashMap實現需求
通過上面的數據我們分析到,針對當前的節點的父節點在上面的記錄已經存在,所以我們這裏引入HashMap記錄已經處理的數據。對於HashMap,key爲節點的ID,value爲當前節點對象。另外我們引入一個存放頂級節點的集合。針對一個對象,如果父級節點爲空,說明此節點爲頂級節點,應該放到頂級節點的結合中;如果不爲空,通過HashMap的get(Object key)方法查詢到當前節點的父節點,然後將當前節點放到父節點的modelList當中。整個代碼邏輯並不複雜。下面是代碼實現:
@Test
public void test1(){
//根節點集合
List<Model> rootModules = new ArrayList<>();
Map<String, Model> tempMap = new HashMap<>();
Model curModel;
String parentId;
for (int i = 0; i < modelList.size(); i++) {
curModel = modelList.get(i);
parentId = curModel.getParentId();
tempMap.put(curModel.getId(),curModel);
//HashMap的key是否包含當前節點的父級節點
if (tempMap.containsKey(parentId)) {
//包含,獲取父級節點並將當前節點加入到ModelList集合中
tempMap.get(parentId).getModelList().add(curModel);
} else {
//如果不存在,則加入根節點集合中
rootModules.add(curModel);
}
}
rootModules.forEach(System.out::println);
}
4.基於棧(Stack)實現需求
4.1 棧的簡述
棧(stack)又名堆棧,它是一種運算受限的線性表。限定僅在表尾進行插入和刪除操作的線性表。這一端被稱爲棧頂,相對地,把另一端稱爲棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成爲新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成爲新的棧頂元素。簡而言之,就是先進後出。如果想不明白,可以想一下子彈夾,先摁進去的子彈最後一個彈出去。
4.2 業務邏輯
1.對於第一個數據,按照上面數據結果的分析,第一個數據肯定是根節點,所以壓入棧頂。
2.對於第二個數據,通過其父節點與棧頂的元素ID進行比較,如果一樣,則將當前元素放入棧頂元素的modleList集合中,同時將第二個元素壓入棧頂。
3.對於第三個數據,通過其父節點與棧頂的元素ID進行比較,此時棧頂元素ID爲2,當前節點父級節點ID爲1,不滿足條件,將棧頂元素彈出,在此通過其父節點與棧頂的元素ID進行比較,此時棧頂元素ID爲1,滿足條件,則將當前元素放入棧頂元素的modleList集合中,同時將第三個元素壓入棧頂。
依次類推,最終遍歷所有的元素,以下是處理流程圖。
4.3.代碼實現
@Test
public void test2(){
List<Model> rootModules = new ArrayList<>();
Stack<Model> moduleStack = new Stack<>();
Model curModule;
for (int i = 0; i < modelList.size(); i++) {
curModule = modelList.get(i);
if(curModule.getParentId() == null){
rootModules.add(curModule);
moduleStack.push(curModule);
}else{
while(moduleStack.peek().getId() != curModule.getParentId()){
moduleStack.pop();
}
moduleStack.peek().getModelList().add(curModule);
moduleStack.push(curModule);
}
}
rootModules.forEach(System.out::println);
}
5.測試
test1是基於HashMap實現的業務邏輯,test2是基於棧實現的業務邏輯,在耗時上基本是棧完虐HashMap。所以瞭解一下數據結構還是很有必要的。