標籤:java 樹 datatable
問題分析:一個關聯式資料庫的表,:
可以看到後面四個欄位:Country,Province,City,Street 具有邏輯上的從屬結構,現在要把這種資料搞成一個樹形結構,:
不是原來的資料轉換而成的,大致就是這個意思,可以想象成,dataTable裡面相同的資料進行單元格合并,然後找到所有的從根到葉子節點的路徑,就算完成任務。JS裡面似乎有很多外掛程式可以實現,但Java中我暫時還沒找到,沒辦法只能自己寫了。從結構上看,應該是一個多叉多級樹形結構,所以在轉換的時候必須具備一定的靈活性,節點的層級也要分明。
首先定義一個node類,描述節點:
public class Node {private String id;private String pId;private String text;private Map<String, Object> nodeValue;private String path;public Node() {}public Node(String id,String pId,String text,String path){this.id = id;this.pId = pId;this.text = text;this.path = path;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getpId() {return pId;}public void setpId(String pId) {this.pId = pId;}public String getText() {return text;}public void setText(String text) {this.text = text;}public Map<String, Object> getNodeValue() {return nodeValue;}public void setNodeValue(Map<String, Object> nodeValue) {this.nodeValue = nodeValue;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}@Overridepublic String toString() {String str = "";if(this.nodeValue!=null){Set<Entry<String,Object>> entrySet = this.nodeValue.entrySet();for (Entry<String, Object> entry : entrySet) {str+=entry.getKey()+"="+entry.getValue();}}return str;}}
簡單說明一下設計初衷:
1,id和pid就不說了,明眼人一眼就看穿了。text表示的是節點當前顯示內容。
2,nodeValue是Map結構,包含從當前節點到根節點的text,比如:三級節點City=QingDao的nodeValue包含說明什麼呢?答案:{city=QingDao,province=ShanDong,country=China}
3,path屬性工作表示節點的地址,或者叫做路徑,用來標識某個節點是否已存在,樣式舉例:/China/ShanDong/QingDao
看具體實作類別:
public class MultiTree {private List<Node> nodeList;private Node rootNode;public List<Node> getNodeList() {return nodeList;}public void setNodeList(List<Node> nodeList) {this.nodeList = nodeList;}public MultiTree() {init();}public MultiTree(List<Node> nodeList, Node rootNode) {this();if (nodeList != null) {this.nodeList = nodeList;}if (rootNode != null) {this.rootNode = rootNode;}}private void init() {nodeList = new ArrayList<Node>();rootNode = new Node("0", "-1", "0", "/");}/** * 把DataTable資料轉換為DataTree,保證path唯一 * @param listMaps * @param args */public void convertListMapToTree(List<Map<String, Object>> listMaps,String... args) {Object value = null;String path = "";Node pNode = null;Node node = null;Map<String, Object> nodeValue = new HashMap<String, Object>();nodeList.add(rootNode);for (Map<String, Object> map : listMaps) {path = "";pNode = getRoot();for (int i = 0;i < args.length;i++) {String key = args[i];value = map.get(key);path += "/" + value;node = findNodeByPath(path);if (node == null) {node = new Node(IdGenerator.uuidGenerator(), pNode.getId(),String.valueOf(value), path);if(i==args.length-1){nodeValue = map;}else{nodeValue = getNodeValueByPath(path,args);}node.setNodeValue(nodeValue);nodeList.add(node);} else {pNode = node;}}}}/** * 根據node path node應該有nodeValue * nodeValue 應該包含父節點的Text,而不應該包含子節點的text,葉子節點應該包含所有的值 * @param path * @param args * @return */private Map<String, Object> getNodeValueByPath(String path, String[] args) {Map<String, Object> nodeValue = new HashMap<String, Object>();String[] values = path.split("/");for (int i = 1;i < values.length;i++) {nodeValue.put(args[i-1], values[i]);}return nodeValue;}public Node getRoot() {return rootNode;}/** * 某個節點的所有子節點 * @param pNode * @return */public List<Node> getChildNodes(Node pNode) {List<Node> childNodes = new ArrayList<Node>();if (pNode == null || pNode.getId() == null) {return childNodes;}for (Node node : nodeList) {if (pNode.getId().equals(node.getpId())) {childNodes.add(node);}}return childNodes;}/** * 根據path尋找node是否存在(因path唯一) * @param path * @return 找到node返回,否則返回null */public Node findNodeByPath(String path) {for (Node node : nodeList) {if (path.equals(node.getPath())) {return node;}}return null;}/** * 從某個節點開始進行深度度遞迴遍曆 * @param pNode */public void recursionTraversal(Node pNode){List<Node> childNodes = getChildNodes(pNode);for (Node node : childNodes) {System.out.println(node.toString());if(getChildNodes(node).size()>0){recursionTraversal(node);}}}}
此類的核心方法是: convertListMapToTree 參數,是資料來源和節點的欄位名稱。
調用方式:
tree.convertListMapToTree(listMaps, "COUNTRY","PROVINCE","CITY","STREET");
執行結果:
//China/China/HeBei/China/HeBei/BaoDing/China/HeBei/BaoDing/street1/China/HeBei/HengShui/China/HeBei/HengShui/street1/China/ShanDong/China/ShanDong/Jian/China/ShanDong/Jian/street1/China/ShanDong/QingDao/China/ShanDong/QingDao/street1/China/ShanDong/YanTai/China/ShanDong/YanTai/street1/Japan/Japan/JiuZhou/Japan/JiuZhou/ChangQi/Japan/JiuZhou/ChangQi/street2/America/America/California/America/California/Los Angeles/America/California/Los Angeles/street3/England/England/Norwich/England/Norwich/Any/England/Norwich/Any/street4
此處有幾個點需要注意:
1,欄位名稱參數傳遞的順序就是節點的層級順序,從高到低,若是寫錯,則結果不準確。
2,一定要有一個根節點,這是樹形結構的必備,程式中已給出預設根節點,也給出了自訂的介面。
3,本程式中,nodeValue中只包含(葉子節點除外)從當前節點到根節點的欄位值,葉子節點包含所有的欄位值,比如本例,葉子節點中也包含ID=1這樣的資料,雖然沒有被應用到節點層級中。
4,判斷path是否存在是關鍵一步,如果該步驟不能準確,則整個程式就以失敗告終。
不足之處:
很多地方都在全域尋找,效率較低,期待後續改進。
java 把DataTable資料類型轉換為樹形結構(多叉樹)