DataTable 轉換為Tree 樹狀結構


一個數據庫表的結構如下:

 

 

 可以看到 province/city/district 這三個字段是邏輯上的主從結構,在展示的時候,有時候會以列表的形式展示,這種方式展示時

不需要做什么特殊處理,如果是以樹狀圖展示時,則需要進行特殊處理。如下圖

 

 

 

首先需要有一個類表示樹結構。

package cn.kanyun;

import java.util.List;
/**
 * 樹節點類
 * @author KANYUN
 *
 */
public class TreeData {

    private String name;
    private Long value;
    private String path;
    private List<TreeData> children;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }

    public List<TreeData> getChildren() {
        return children;
    }

    public void setChildren(List<TreeData> children) {
        this.children = children;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

}

有了樹的節點類之后,接下來我們就需要將數據庫中的表的記錄查出來,然后將結果集放到該節點類對象中。

接下來查看測試類

 

package cn.kanyun;

import cn.hutool.db.Db;
import cn.hutool.db.Entity;

import java.sql.SQLException;
import java.util.*;

import com.google.gson.Gson;

public class MainTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {

            List<Entity> result = Db.use()
                    .query("SELECT\r\n" + "    province,\r\n" + "    city,\r\n" + "    district ,\r\n"
                            + "    SUM(population) AS population\r\n" + "FROM\r\n"
                            + "    area \r\n" + "WHERE\r\n"
                            + "    province = '河南省' or province = '浙江省'\r\n" + "GROUP BY\r\n" + "    province,\r\n"
                            + "    city,\r\n" + "    district ");
//            構建樹形的字段,必須出現在查詢的SQL中,也就是說你想用哪幾個字段來構造父子關系,同時也說明了,可以使用任意字段構造樹形結構(是否有意義則另說)
//            其add()的順序也表示了每個字段的父子關系
            List<String> dimensions = new ArrayList<String>();
            dimensions.add("province");
            dimensions.add("city");
            dimensions.add("district");
            TreeHandler treeHandler = new TreeHandler();
            System.out.println("總數量:" + result.size());
            List<TreeData> treeDataList = new ArrayList<>();
//            root節點集合
            Set<String> set = new HashSet<>();
//            所有節點集合
            Set<String> paths = new HashSet<>();
//            遍歷結果集
            for (Entity stringObjectMap : result) {
//                找到root節點的值
                String key = String.valueOf(stringObjectMap.get(dimensions.get(0)));
                TreeData treeData = null;
//                判斷root節點是否被添加過
                if (!set.contains(key)) {
                    treeData = new TreeData();
                    treeData.setName(key);
                    treeData.setPath(key);
                    set.add(key);
                    treeDataList.add(treeData);
                } else {
//                    如果當前root節點被添加過,則找過那個節點
                    for (TreeData node : treeDataList) {
                        if (node.getName().equals(key)) {
                            treeData = node;
                            break;
                        }
                    }
                }
//                待計數的字段名(也需要出現的查詢SQL中)
                String v_key = "population";
//                處理樹形結構
                treeHandler.tree(paths, treeData, (Map<String, Object>) stringObjectMap, dimensions, v_key);
            }

            
            CountHandler countHandler = new CountHandler();
//            進行計數
            countHandler.count(treeDataList);
            
            Gson gson = new Gson();
            String vaString = gson.toJson(treeDataList);
            System.out.println(vaString);

        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

 

 

 

測試類中,除了數據庫查詢操作外(數據庫查詢使用了HuTool工具庫),還有兩個比較重要的類 TreeHandler/CountHandler

其中TreeHandler主要用來處理樹形結構

package cn.kanyun;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.hutool.db.sql.Condition;
/**
 * 樹形處理類
 * @author KANYUN
 *
 */
public class TreeHandler {

    /**
     * 構造樹的方法
     * @param paths
     * @param treeData
     * @param rowData
     * @param dimensions
     * @param v_key
     */
    public void tree(Set<String> paths, TreeData treeData, Map<String, Object> rowData, List<String> dimensions,String v_key) {

//        最低等級
        String lowLevel = dimensions.get(dimensions.size()-1);
//        記錄當前的path,path的作用是用來判斷層級
        String currentPath = "";
        for (String dimension : dimensions) {
            String cellData = String.valueOf(rowData.get(dimension));
            currentPath += cellData;

            if (paths.contains(currentPath)) {
//                如果當前path包含在paths集合中,說明該path已經加入了樹對象中,則繼續循環
                currentPath += "/";
                continue;
            }
            System.out.println(currentPath);
            paths.add(currentPath);
            if (!dimension.equals(lowLevel)) {
//                如果當前key不是最低等級,則執行addChildNode方法
                addChildNode(treeData, cellData, currentPath);
            } else {
//                如果當前key是最低等級,則執行addNode()方法
                int measureValue = 0;
                try {
                    measureValue = Integer.parseInt(String.valueOf(rowData.get(v_key)));
                }catch(Exception e) {
                    System.out.println("============");
                    System.out.println(rowData);
                    System.out.println(rowData.get(v_key));
                    throw e;
                }
                
                addNode(treeData, cellData, currentPath, measureValue);
            }
            currentPath += "/";
        }

    }

    /**
     * @return void
     * @Description 添加葉子節點,即最低級的節點
     * @Date 18:16 2020/8/31
     * @Param [treeData, cellData, beforeName, measureValue]
     **/
    private void addNode(TreeData treeData, String cellData, String currentPath, long measureValue) {

        if ((treeData.getPath() + "/" + cellData).equals(currentPath)) {
//            還是先判斷路徑是否一致,一致說明待插入節點是當前節點的子節點
            TreeData data = new TreeData();
            data.setName(cellData);
            data.setValue(measureValue);
            data.setPath(currentPath);
            if (treeData.getChildren() == null) {
                List<TreeData> treeDataList = new ArrayList<>();
                treeData.setChildren(treeDataList);
            }
            treeData.getChildren().add(data);
        } else {
            if (treeData.getChildren() == null) {
//                判斷當前節點是否為空,為空設置其value,然后直接返回
                treeData.setValue(measureValue);
                return;
            }
            for (TreeData child : treeData.getChildren()) {
//                繼續遞歸
                addNode(child, cellData, currentPath, measureValue);
            }
        }
    }

    /**
     * @return boolean
     * @Description 添加節點,遞歸調用
     * @Date 10:42 2020/8/31
     * @Param [treeData, name]
     **/
    public void addChildNode(TreeData treeData, String cellData, String currentPath) {
        
        if (treeData.getPath().equals(currentPath)) {
//            如果當前的節點路徑,和傳遞過來的節點路徑一致,則直接返回
            return;
        }

        if ((treeData.getPath() + "/" + cellData).equals(currentPath)) {
//            判斷(當前的節點路徑 + "/" + cellData) 與傳遞過來的帶插入的路徑是否一致,如果一致,說明待插入的節點是當前節點的子節點
            
//            構建待插入的節點對象
            TreeData data = new TreeData();
            data.setName(cellData);
            data.setPath(currentPath);
            if (treeData.getChildren() == null) {
//                判斷當前節點是否存在子節點list,存在則直接插入,不存在則先構造子節點list
                List<TreeData> treeDataList = new ArrayList<>();
                treeData.setChildren(treeDataList);
            }
            treeData.getChildren().add(data);
        }
        
//        走到這里說明沒有發現能插入的節點
        if (treeData.getChildren() != null) {
            for (TreeData tree : treeData.getChildren()) {
//                繼續遍歷遞歸
                addChildNode(tree, cellData, currentPath);
            }
        }

    }

}

 

需要注意的是測試類中的每一行結果集,都會調用TreeHandler中的tree()方法,在tree()方法中,會遍歷該結果集中的每一個字段。

先判斷該字段的值是否被添加到了樹狀結構中,如果沒有則繼續添加,如果已經添加過,則進行下一個字段。需要注意的是如果判斷一個節點是否是

另外一個節點的子節點。

這里使用的是TreeData類中的 path字段。通過組裝path字段,來判斷父子關系

 

如圖,該行數據包含三個字段,也就是三個節點。

其中父節點的path 為 “山東省”

中間節點解的path 為 “山東省/德州市”

葉子節點的 path 為“山東省/德州市/樂陵市”

 由於每次遍歷時,就已經知道了當前節點 的path 和 value ,因此判斷待插入節點是否與當前節點是父子關系是,就可以判斷當前節點的

path + "/" + 待插入節點的 value 是否與 待插入節點的path一致,如果一致說明當前節點是待插入節點的父節點,如果不是則繼續遞歸判斷。

該類的主要方法為addNode()/addChildNode()方法。

 

其中CountHandler用來計數

package cn.kanyun;

import java.util.List;
/**
 * 計數處理類
 * @author KANYUN
 *
 */
public class CountHandler {
    
    /**
     * 外部調用該方法
     * @param treeDataList root節點下的數組
     */
    public void count(List<TreeData> treeDataList) {
        for (TreeData treeData : treeDataList) {
            if (treeData.getValue() == null) {
                groupCount(treeData);
//                當上面方法執行完成,就說明當前節點下的所有子節點都已經有值了,因此直接將該節點進行計數
                calc(treeData);
            }

        }
    }

    /**
     * 分組計數(遞歸方法)
     * @param treeData
     */
    public void groupCount(TreeData treeData) {
//        如果當前節點的value為空
        if (treeData.getValue() == null) {
//            則判斷是否可以為該節點進行計數(其主要依據是看該節點的子節點的value是否都有值)
            if (!isCount(treeData)) {
//                如果該節點不能被計數,則遍歷該節點的所有子節點,進行遞歸
                List<TreeData> treeDataList = treeData.getChildren();
                if (treeDataList == null) return ;
                for (TreeData data : treeDataList) {
//                    遞歸方法
                    groupCount(data);
                }
            } else {
//                如果該節點可以計數,則進行計數
                calc(treeData);
            }
        }

    }

    /**
     * 統計計數,當isCount()方法返回true時執行
     * @param treeNode
     */
    public void calc(TreeData treeNode) {
        long i = 0;
        List<TreeData> treeDataList = treeNode.getChildren();
        if (treeDataList == null) return ;
        for (TreeData treeData : treeDataList) {
            i += treeData.getValue();
        }
        treeNode.setValue(i);
    }

    /**
     * 判斷是否可以進行計數操作
     * @param treeNode
     * @return
     */
    public boolean isCount(TreeData treeNode) {
        List<TreeData> treeDataList = treeNode.getChildren();
        if (treeDataList == null) return false;
//        如果該方法的入參對象,其所有子節點的value都不為空,說明可以為當前入參對象進行計數了
        for (TreeData treeData : treeDataList) {
            if (treeData.getValue() == null) {
                return false;
            }
        }
        return true;
    }
}

 

 

從第一張圖上我們看出,該表最后一個字段表示的是人口數,那么我們組裝完tree之后,非葉子節點的 value將為空,因為我們取到的value都是葉子節點的value。

 

 即我們現在知道“山東省/德州市/樂陵市”的人口數是420406 但是“山東省/德州市”的人口數是不知道的 “山東省”的也是不知道的,因此我們需要統計非葉子節點的值

在這里需要認清楚葉子節點是有值的,因此需要不斷遞歸的給父節點算值。

具體步驟是。先判斷一個節點是否有值,有值自然不需要管了,如果沒有值,則遍歷其所有子節點是否有值,如果所有子節點都有值,則可以為當前節點計算值,如果

當前節點的所有節點不全都有值,則繼續遞歸當前節點的子節點,直至所有子節點都有值

 

代碼及數據下載


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM