一些树形结构的数据,经常是以 id、parent_id、其他属性这种方式存放在数据库里的,直接查询出来的就是这些带有id、parent_id值的javabean的list,并不是树形的结构,这次项目需要,我直接把对应这种情况的写成一个通用的类。大概的思路就是用map把id还有子节点映射起来,这样就可以做到从树的某一节点往下遍历。另外用一个map把id还有节点本身映射起来,因为每个节点知道它的parent_id,通过这个map就能得到父节点,这样就可以往上遍历了。
直接贴代码:
1.先是一个接口定义了一些访问树的方法,大部分都需要把map作为参数传递进去,然后再定义两个从list得到map的方法。
BaseTree.java
package com.csair.csm.util; import java.util.List; import java.util.Map; /** * 基础树服务接口 * <br>!注意: * <br>不建议使用dubbo远程调用该接口的方法(涉及大量数据传输(所有节点的List、Map);find方法涉及回调,需使用配置文件注册服务) * @author abo * @param <N>树节点 * @param <K>树节点id(唯一标识) */ public interface BaseTree<N, K> { /** * 获取所有节点key - 子节点List 映射Map * @param nodeList 所有节点List * @return */ Map<K, List<N>> getAllNodeAsMap(List<N> nodeList); /** * 获取所有节点的key - 自己的映射Map * @param nodeList 所有节点List * @return */ Map<K, N> getAllNodeSelfMap(List<N> nodeList); /** * 从某节点开始往上级搜索 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @param selfMap 所有节点的key - 自己的映射(可通过getAllNodeSelfMap获取) * @return 匹配条件的节点集合(按层级从大到小排列) */ List<N> findUp(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, N> selfMap); /** * 从某节点开始往上级搜索,找到一个符合条件的就返回 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @param selfMap 所有节点的key - 自己的映射(可通过getAllNodeSelfMap获取) * @return 匹配条件的第一个节点(层级大的优先) 找不到则返回null */ N findUpFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, N> selfMap); /** * 从某节点开始往下级搜索 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @return 匹配条件的节点集合(按层级从小到大排列) */ List<N> findDown(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, List<N>> childMap); /** * 从某节点开始往下级搜索,找到一个符合条件的就返回 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @return 匹配条件的第一个节点(层级小的优先) 找不到则返回null */ N findDownFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, List<N>> childMap); /** * 获得某节点的所有祖先节点 * @param node 当前节点 * @param selfMap 所有节点的key - 自己的映射(可通过getAllNodeSelfMap获取) * @param needSelf 是否包含自己 * @return 祖先节点集合(按层级从大到小排列) */ List<N> getParentsNode(N node, Map<K, N> selfMap, boolean needSelf); /** * 获取某节点的所有后代节点 * @param node 当前节点 * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @param needSelf 是否包含自己 * @return 后代节点集合(按层级从小到大排列) */ List<N> getSubNode(N node, Map<K, List<N>> childMap, boolean needSelf); /** * 获取某节点下面的所有叶子 * @param node 当前节点 * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @return 叶子节点集合(如果自己是叶子,则集合中为自己) */ List<N> getSubLeaf(N node, Map<K, List<N>> childMap); /** * 获取某节点的父节点(直接上级) * @param node * @param selfMap 所有节点的key - 自己的映射(可通过getAllNodeSelfMap获取) * @return 没有则返回null */ N getParent(N node, Map<K, N> selfMap); /** * 获取某节点的所有孩子(直接下级) * @param node * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @return 没有则返回null */ List<N> getChildren(N node, Map<K, List<N>> childMap); /** * 判断一个节点是不是叶子 * @param node * @param childMap 所有节点key - 子节点List 映射(可通过getAllNodeAsMap获取) * @return */ boolean isLeaf(N node, Map<K, List<N>> childMap); }
2.再定义一个抽象类实现这个接口的所有方法,保留两个抽象方法,用来具体实现获取每个节点的id、parent_id(因为并不知道具体类型id、parent_id的属性名是什么)。
BaseTreeImpl.java
package com.csair.csm.util; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 基础树服务实现类 * @author abo * @param <N>树节点 * @param <K>树节点id(唯一标识)(K须具有有效的hashCode与equals方法) */ public abstract class BaseTreeImpl<N, K> implements BaseTree<N, K> { /** * 获取节点唯一标识方法 * @param node * @return */ protected abstract K getKey(N node); /** * 获取节点父节点唯一标识方法 * @param node * @return */ protected abstract K getParentKey(N node); @Override public Map<K, List<N>> getAllNodeAsMap(List<N> nodeList) { //map初始化容量(假设为满二叉树) int initialCapacity = Math.max((int) ((nodeList.size() - 1)/2.0f/.75f) + 1, 16); //建一个map来存放 父节点no到子节点列表 的映射 Map<K, List<N>> childMap = new HashMap<>(initialCapacity); //遍历一遍 for (N node : nodeList) { //取出它父节点的子节点列表 List<N> childList = childMap.get(getParentKey(node)); //如果还没有子节点列表,就新建一个 if (childList == null) { childList = new ArrayList<>(); childMap.put(getParentKey(node), childList); } //把自己放进去 childList.add(node); } return childMap; } @Override public Map<K, N> getAllNodeSelfMap(List<N> nodeList) { //map初始化容量 int initialCapacity = Math.max((int) (nodeList.size()/.75f) + 1, 16); //新建一个map Map<K, N> selfMap = new HashMap<>(initialCapacity); //遍历所有 for (N node : nodeList) { //放进去map selfMap.put(getKey(node), node); } return selfMap; } @Override public List<N> findUp(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, N> selfMap) { //新建一个结果集 List<N> results = new ArrayList<>(); //这个变量去存储每个节点 N currentNode; //先得到第一个 currentNode = node; //需不需要搜索第一个 if (needSelf && searcher.search(currentNode)) { results.add(currentNode); } //逐级往上搜索 do { //取到父节点 currentNode = getParent(currentNode, selfMap); if (currentNode == null) { //结束了 break; } //搜索 if (searcher.search(currentNode)) { results.add(currentNode); } } while (true); return results; } @Override public N findUpFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, N> selfMap) { // 这个变量去存储每个节点 N currentNode; // 先得到第一个 currentNode = node; // 需不需要搜索第一个 if (needSelf && searcher.search(currentNode)) { //返回 return currentNode; } // 逐级往上搜索 do { // 取到父节点 currentNode = getParent(currentNode, selfMap); if (currentNode == null) { // 结束了 break; } // 搜索 if (searcher.search(currentNode)) { //返回 return currentNode; } } while (true); //这里一定是null了 return currentNode; } @Override public List<N> findDown(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, List<N>> childMap) { //先获得所有后代 List<N> childList = getSubNode(node, childMap, needSelf); //新建一个结果集 List<N> resultsList = new ArrayList<>(); //逐个去搜索 for (N childNode : childList) { if (searcher.search(childNode)) { resultsList.add(childNode); } } return resultsList; } @Override public N findDownFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf, Map<K, List<N>> childMap) { // 如果需要找自己 if (needSelf && searcher.search(node)) { // 返回 return node; } // 建一个集合用来做广度遍历 List<N> nodeList = new ArrayList<>(); // 先把自己放进去 nodeList.add(node); // 进行广度遍历 int i = nodeList.size() - 1; // 遍历下标 List<N> childList; // 存放直接下级 while (true) { // 获取孩子 childList = getChildren(node, childMap); if (childList != null) { // 存在就加进去 nodeList.addAll(childList); } i++; if (i >= nodeList.size()) { // 如果越界了,就跳出 break; } else { // 否则node指向下一个 node = nodeList.get(i); // 找下一个 if (searcher.search(node)) { // 匹配就直接返回 return node; } } } // 到这里说明没找到,返回null return null; } @Override public List<N> getParentsNode(N node, Map<K, N> selfMap, boolean needSelf) { //无条件往上搜索 return findUp(node, new BaseTreeSearcher<N>() { @Override public boolean search(N node) { //直接返回true return true; } }, needSelf, selfMap); } @Override public List<N> getSubNode(N node, Map<K, List<N>> childMap, boolean needSelf) { //建一个空的结果 List<N> nodeList = new ArrayList<>(); //如果需要自己 if (needSelf) { //先把自己放进去 nodeList.add(node); } //进行广度遍历找到所有后代 int i = nodeList.size() - 1; //遍历下标 List<N> childList; //存放直接下级 while(true){ //获取孩子 childList = getChildren(node, childMap); if (childList != null) { //存在就加进去 nodeList.addAll(childList); } i++; if (i >= nodeList.size()) { //如果越界了,就跳出 break; } else { //否则node指向下一个 node = nodeList.get(i); } } return nodeList; } @Override public List<N> getSubLeaf(N node, final Map<K, List<N>> childMap) { // 搜索它的后代 return findDown(node, new BaseTreeSearcher<N>() { @Override public boolean search(N node) { // 是不是叶子 return isLeaf(node, childMap); } }, true, childMap); } @Override public N getParent(N node, Map<K, N> selfMap) { return selfMap.get(getParentKey(node)); } @Override public List<N> getChildren(N node, Map<K, List<N>> childMap) { return childMap.get(getKey(node)); } @Override public boolean isLeaf(N node, Map<K, List<N>> childMap) { if (getChildren(node, childMap) == null) { // 没有下级,说明是叶子 return true; } return false; } }
3.这个接口只是用来在对树进行搜索时提供回调,实现具体的搜索逻辑。
BaseTreeSearcher.java
package com.csair.csm.util; /** * 自定义树搜索器 * @author abo * @param <N> 树节点 */ public interface BaseTreeSearcher<N> { /** * 当该方法返回true时,表示匹配到该节点 * @param node 当前树节点 * @return */ boolean search(N node); }
这样基本就搞定了,在具体使用的时候,一种方法是接口和类分别继承1和2,直接提供这些树形操作的方法。
还有一种像这样:
private static final BaseTree<OrgTreeNode, String> baseTree = new BaseTreeImpl<OrgTreeNode, String>() { @Override protected String getKey(OrgTreeNode node) { return node.getOrgNo(); } @Override protected String getParentKey(OrgTreeNode node) { return node.getParentOrgNo(); } };
直接new一个静态常量,然后就可以像一个工具一样调用它的方法了。
4.后来考虑到每次调用都要把map作为参数传递也不太方便,又增加了一个类,就是用树的所有节点构造一棵树,然后就可以直接在树上进行操作了。
Tree.java
package com.csair.csm.util; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; /** * 一棵树 * @author abo * @param <N>树节点 * @param <K>树节点id(唯一标识)(K须具有有效的hashCode与equals方法) */ public abstract class Tree<N, K> { /** * 树上所有节点的列表 */ private List<N> allNodes; /** * 树根 */ private N root; /** * 节点到其子节点的映射 */ private Map<K, List<N>> childMap; /** * 节点到自身的映射 */ private Map<K, N> selfMap; public List<N> getAllNodes() { return allNodes; } public void setAllNodes(List<N> allNodes) { this.allNodes = allNodes; } public N getRoot() { if (root == null) { // 随便找一个的最上级的祖先,就是根节点 List<N> tempList = baseTree.getParentsNode(this.getAllNodes().get(0), this.getSelfMap(), true); root = tempList.get(tempList.size() - 1); } return root; } public void setRoot(N root) { this.root = root; } public Map<K, List<N>> getChildMap() { if (childMap == null) { // 初始化 childMap = baseTree.getAllNodeAsMap(this.getAllNodes()); } return childMap; } public void setChildMap(Map<K, List<N>> childMap) { this.childMap = childMap; } public Map<K, N> getSelfMap() { if (selfMap == null) { selfMap = baseTree.getAllNodeSelfMap(this.getAllNodes()); } return selfMap; } public void setSelfMap(Map<K, N> selfMap) { this.selfMap = selfMap; } /** * 获取节点唯一标识方法 * @param node * @return */ protected abstract K getKey(N node); /** * 获取节点父节点唯一标识方法 * @param node * @return */ protected abstract K getParentKey(N node); /** * 树形操作的工具 */ private final BaseTree<N, K> baseTree = new BaseTreeImpl<N, K>() { @Override protected K getKey(N node) { return Tree.this.getKey(node); } @Override protected K getParentKey(N node) { return Tree.this.getParentKey(node); } }; /** * 构造方法,传入所有树节点来构造一棵树 * @param allNodes */ public Tree(Collection<N> allNodes) { this.setAllNodes(new ArrayList<N>(allNodes)); } /** * 从某节点开始往上级搜索 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @return 匹配条件的节点集合(按层级从大到小排列) */ public List<N> findUp(N node, BaseTreeSearcher<N> searcher, boolean needSelf) { return baseTree.findUp(node, searcher, needSelf, this.getSelfMap()); } /** * 从某节点开始往上级搜索,找到一个符合条件的就返回 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @return 匹配条件的第一个节点(层级大的优先) 找不到则返回null */ public N findUpFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf) { return baseTree.findUpFirst(node, searcher, needSelf, this.getSelfMap()); } /** * 从某节点开始往下级搜索 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @return 匹配条件的节点集合(按层级从小到大排列) */ public List<N> findDown(N node, BaseTreeSearcher<N> searcher, boolean needSelf) { return baseTree.findDown(node, searcher, needSelf, this.getChildMap()); } /** * 从某节点开始往下级搜索,找到一个符合条件的就返回 * @param node 当前节点 * @param searcher 自定义搜索条件 * @param needSelf 是否搜索自己 * @return 匹配条件的第一个节点(层级小的优先) 找不到则返回null */ public N findDownFirst(N node, BaseTreeSearcher<N> searcher, boolean needSelf) { return baseTree.findDownFirst(node, searcher, needSelf, this.getChildMap()); } /** * 获得某节点的所有祖先节点 * @param node 当前节点 * @param needSelf 是否包含自己 * @return 祖先节点集合(按层级从大到小排列) */ public List<N> getParentsNode(N node, boolean needSelf) { return baseTree.getParentsNode(node, this.getSelfMap(), needSelf); } /** * 获取某节点的所有后代节点 * @param node 当前节点 * @param needSelf 是否包含自己 * @return 后代节点集合(按层级从小到大排列) */ public List<N> getSubNode(N node, boolean needSelf) { return baseTree.getSubNode(node, this.getChildMap(), needSelf); } /** * 获取某节点下面的所有叶子 * @param node 当前节点 * @return 叶子节点集合(如果自己是叶子,则集合中为自己) */ public List<N> getSubLeaf(N node) { return baseTree.getSubLeaf(node, this.getChildMap()); } /** * 获取某节点的父节点(直接上级) * @param node * @return 没有则返回null */ public N getParent(N node) { return baseTree.getParent(node, this.getSelfMap()); } /** * 获取某节点的所有孩子(直接下级) * @param node * @return 没有则返回null */ public List<N> getChildren(N node) { return baseTree.getChildren(node, this.getChildMap()); } /** * 判断一个节点是不是叶子 * @param node * @return */ public boolean isLeaf(N node) { return baseTree.isLeaf(node, this.getChildMap()); } }