前言
最近遇到一個顯示樹結構的問題,需要將結果以樹的形式進行展示,想着這個東西其實很普遍很常見,所以在網上找了兩個方法+自己想的一個笨辦法,現此進行簡單思路說明總結。
參考鏈接
- https://blog.csdn.net/ldllovegyh/article/details/102692948
- https://blog.csdn.net/weixin_39819191/article/details/84180652?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase
實現方法
給定的Dept類實例如下圖所示,以下方法以圖中Dept實例爲例。Dept屬性分別爲id、name、pid,根據pid鎖定其父節點
public class Dept {
private Long id;
private Long parentId;
private String name;
}
Dept dept1 = new Dept(1L,0L,"IT");
Dept dept2 = new Dept(2L,1L,"code");
Dept dept3 = new Dept(3L,1L,"design");
Dept dept4 = new Dept(4L,0L,"ceo");
Dept dept5 = new Dept(5L,4L,"manager");
Dept dept6 = new Dept(6L,3L,"ui");
Dept dept7 = new Dept(7L,0L,"hr");
基本思路是定義一個樹結構,然後對這個樹結構重排列實現以pid爲線索的父子節點關聯。(關鍵屬性在於List<DeptTree> childTree,通過遞歸生成樹的方法不斷將子節點加入到這個List中)
public class DeptTree {
private Long id;
private String name;
private Long pid;
private List<DeptTree> childTree;
}
方法1
基本思路:遍歷DeptTreeList,然後首先從Pid==0L開始生成childTree結構,生成的過程中使用遞歸該方法不斷遍歷。例如先遍歷到dept1,然後執行buildTree(treeList,1L)將結果賦值給IT這個deptTree1;第二次執行方法的時候遍歷到dept2符合條件tree.getParentId==rootId,執行buildTree(treeList,2L)將結果賦值code這個deptTree2;第三層執行方法沒有符合if條件的所以遞歸結束迴歸第二層遞歸中遍歷到dept3...知道所有的treeList遍歷完畢。
這種方法我測試的時候出現了StackOver但是思路是通的,這塊單純記錄下代碼。
public static List<DeptTree> buildTree(List<DeptTree> treeList, Long rootId){
List<DeptTree> newTreeList = new ArrayList<Dept>();
for(DeptTree tree : treeList){
if(tree.getParentId() == rootId){//根機構
tree.setChild(buildTree(treeList,tree.getId()));
newTreeList.add(tree);
}
}
return treeList;
}
方法2
基本思路:和方法1類似。獲取所有的pid,然後遍歷pid。判斷當pid==0的時候創建DeptTree,然後判斷其是否有子節點,如果有的話說明是第一層樹結構,執行deptTree對象的setChild方法同時方法參數爲遞歸調用buildTree方法,buildTree方法的參數爲這個子節點的id;如果pid!=0,那麼說明是非第一層樹結構,直接生成一個新的deptTree,順便判斷其是否有子節點,如果有則遞歸。
這個方法其實和方法1思路差不多,只不過實際項目中與數據庫相關聯爲了簡便,直接以id爲關鍵進行查詢和樹結構構建。
public List<DeptTree> buildTree(List<Long> rootIds){
List<DeptTree> deptTreeList = new ArrayList<>();
Collections.sort(rootIds);//從小到大排序
for(Long id : rootIds){
if(id==0L){
//獲取子機構
List<Dept> rootDepts = selectDeptListByParentId(id);//根據pid獲取所有的子節點ids
if(!rootDepts.isEmpty()){
//遍歷機構
for(Dept rootDept : rootDepts){
//Dept轉爲DeptTree
DeptTree deptTree = new DeptTree();
BeanUtils.copyProperties(rootDept, deptTree);
//獲取子機構id
List<Long> childIds = selectDeptIdsByParentId(rootDept.getId());
if(!childIds.isEmpty()){
deptTree.setChildren(buildTree(childIds));
}
deptTreeList.add(deptTree);
}
}
}else{
Dept dept = departmentMapper.selectById(id);//根據id獲取所有的節點
DeptTree deptTree = new DeptTree();
BeanUtils.copyProperties(dept, deptTree);
List<Long> childIds = selectDeptIdsByParentId(dept.getId());
if(!childIds.isEmpty()){
deptTree.setChildren(buildTree(childIds));
}
deptTreeList.add(deptTree);
}
}
return deptTreeList;
}
方法3
基本思路:分別構建根節點和非根節點,利用流處理遍歷非根節點列表過濾掉與給定節點pid和id不相同的無關節點、過濾掉id爲給定節點pid的節點,然後將節點中pid==給定節點id的節點遍歷加入到childList,最後加入到根節點的setChildren方法中即可。
相比方法2的多次查詢操作,方法3只需要查詢一次查詢到所有的Dept即可,所以相對來講性能較高。
public class TreeToolUtils {
private List<DeptTree> rootList; //根節點對象存放到這裏
private List<DeptTree> bodyList; //其他節點存放到這裏,可以包含根節點
public TreeToolUtils(List<DeptTree> rootList, List<DeptTree> bodyList) {
this.rootList = rootList;
this.bodyList = bodyList;
}
public List<DeptTree> getTree(){ //調用的方法入口
if(bodyList != null && !bodyList.isEmpty()){
//聲明一個map,用來過濾已操作過的數據
Map<Long,Long> map = Maps.newHashMapWithExpectedSize(bodyList.size());
rootList.forEach(beanTree -> getChild(beanTree,map));//傳遞根對象和一個空map
return rootList;
}
return null;
}
public void getChild(DeptTree beanTree,Map<Long,Long> map){
List<DeptTree> childList = Lists.newArrayList();
bodyList.stream()
.filter(c -> !map.containsKey(c.getId()))//map內不包含子節點的code(過濾掉id和pid不含子節點的節點)
.filter(c ->c.getParentId().equals(beanTree.getId()))//子節點的父id==根節點的code 繼續循環(過濾掉pid含子節點的節點)
.forEach(c ->{
map.put(c.getId(),c.getParentId());//當前節點code和父節點id
getChild(c,map);//遞歸調用
childList.add(c);
});
beanTree.setChildren(childList);
}
}
最後執行的結果如下:將結果通過網上的JSON轉換器轉換成JSON格式
[{
"id": 1,
"name": "IT",
"parentId": 0,
"childTree": [{
"id": 2,
"name": "code",
"parentId": 1,
"childTree": []
},
{
"id": 3,
"name": "design",
"parentId": 1,
"childTree": [{
"id": 6,
"name": "ui",
"parentId": 3,
"childTree": []
}]
}
]
},
{
"id": 4,
"name": "ceo",
"parentId": 0,
"childTree": [{
"id": 5,
"name": "manager",
"parentId": 4,
"childTree": []
}]
},
{
"id": 8,
"name": "hr",
"parentId": 0,
"childTree": []
}
]
問題記錄
1. IDEA導入Maven包顯示Reimport Maven Failure.查看IDEA的日誌發現錯誤:java.lang.RuntimeException: com.google.inject.CreationException: Unable to create injector, see the following errors:
基本差不多是maven版本和IDEA版本不兼容導致的,解決方法就是降低maven的版本。我電腦是maven3.6.2,IDEA2019.2.1x,將maven換成3.5.4問題解決。(儘量不要使用太多新的版本軟件,避免出現軟件間版本不兼容的問題)
2. 方法3中出現的Maps等Collection使用的是Google下的一個包,需要手動下載然後導入項目中,下載鏈接:https://mvnrepository.com/artifact/com.google.collect/com.springsource.com.google.common.collect/0.8.0.20080820
3. 爲簡化對象的屬性複製,使用BeanUtils類(Apache commons組件的成員之一,主要用於簡化JavaBean封裝數據的操作)的copyProperties方法將Dept類的屬性複製到DeptTree中,相當於↓
DeptTree tree = new DeptTree();
tree.setId(Dept.getId());
tree.setName(Dept.getName());
tree.setPid(Dept.getPid());
注意需要導入包,下載鏈接:http://commons.apache.org/proper/commons-beanutils/
關於BeanUtils的學習參考鏈接:https://www.cnblogs.com/syncmr/p/10523576.html
總結
遇到問題先自己想想,然後在網上找性能更高的實現方法並及時總結,多學習大佬的編程邏輯和思路,提升思維水平。別拖拉,及時總結