組織樹結構後端實現

前言

最近遇到一個顯示樹結構的問題,需要將結果以樹的形式進行展示,想着這個東西其實很普遍很常見,所以在網上找了兩個方法+自己想的一個笨辦法,現此進行簡單思路說明總結。

參考鏈接

實現方法

給定的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

總結

遇到問題先自己想想,然後在網上找性能更高的實現方法並及時總結,多學習大佬的編程邏輯和思路,提升思維水平。別拖拉,及時總結

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章