需求:
项目中经常会用到树构建,比如工程类型数据构建,测试中一个工程类型加节点数据,约两万条,普通的递归构建树算法计算次内存暂用高,CPU占用高,耗时久,项目用时1分钟左右,数据量上万后基本无法使用。
思路:
普通的递归构建中,每次递归查找子节点或父节点都需要遍历整个集合,效率低下,LinkedHashMap底层是由红黑树组成,数据量越大,查找效率越明显,耗时从1分钟减少到了5秒左右,效率提升了10倍有余。
算法描述:
1、把所有数据放入LinkedHashMap中
2、然后遍历LinkedHashMap中的所有值,并从该LinkedHashMap中查找当前节点的父节点,把当前节点加入父节点的孩子中
3、从LinkedHashMap中将已经加入父节点子集中的节点移除掉,并将移除节点后的LinkedHashMap作为下一个递归的参数继续调用
4、直到LinkedHashMap中所有的节点都再找不到父节点为止,这些剩下的根节点就是一棵构造好的树
优点:时间复杂度和空间复杂度低,效率高速度快,处理大数据量效果好
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author fcc
* @date 2024/7/9
* @param <T>
*/
public class TreeFactory<T extends TreeNodePlus<String>> {
// 私有构造函数,防止外部实例化
private TreeFactory() {}
/**
* 公共方法,用于构建树形结构
*
* @param nodeList 实现了 TreeNode 接口的节点列表
* @return 包含根节点的列表(可能包含多个根节点)
*/
public static <T extends TreeNodePlus<String>> List<T> buildTree(List<T> nodeList) {
if (nodeList == null || nodeList.isEmpty()) {
return new ArrayList<>();
}
Map<String, T> nodeMap = new LinkedHashMap<>(nodeList.size());
for (T node : nodeList) {
nodeMap.put(node.getId(), node);
}
return findRoots(nodeMap);
}
/**
* 私有方法,用于递归向上查找父节点,并确定根节点
*
* @param nodeMap 节点映射
* @return 包含所有根节点的列表
*/
private static <T extends TreeNodePlus<String>> List<T> findRoots(Map<String, T> nodeMap) {
List<String> removeNodes = new ArrayList<>();
for (T node : nodeMap.values()) {
T pNode = nodeMap.get(node.getParentId());
if (pNode != null && !pNode.getId().equals(node.getId())) {
pNode.addChild(node);
removeNodes.add(node.getId());
}
}
// 从映射中移除所有已处理的节点
for (String id : removeNodes) {
nodeMap.remove(id);
}
// 剩余在map中的节点即为根节点
return new ArrayList<>(nodeMap.values());
}
// 其他方法和字段(如果有的话)...
}
/**
* @author fcc
* @date 2024/7/9
* @param <T>
*/
public interface TreeNodePlus<T> {
T getId(); // 获取节点的唯一标识符
T getParentId(); // 获取父节点的唯一标识符
void addChild(TreeNodePlus<T> child); // 向节点添加子节点
}
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class EngineeringTree implements TreeNodePlus<String> {
private String sid;
private String pid;
private List<EngineeringTree> children = new ArrayList<>();
// 构造器、getter 和 setter 省略
@Override
public String getId() {
return sid; //项目特殊业务 因为id有重复所以附加了sid 正常id就可以
}
@Override
public String getParentId() {
return pid;
}
@Override
public void addChild(TreeNodePlus<String> child) {
// 由于 child 实际上是 EngineeringTree 类型(因为我们知道它实现了 TreeNode<String>),我们可以安全地转换
this.children.add((EngineeringTree) child);
}
@ApiModelProperty("id")
private String id;
@ApiModelProperty("编号")
private String code;
@ApiModelProperty("工程实体类型")
private String type;
@ApiModelProperty("名称")
private String name;
@ApiModelProperty("是否是进度节点")
private Integer isSchedule;
@ApiModelProperty("是否配置")
private Integer isConfirmation;
}
public List<EngineeringTree> getTableTree(EngineeringVO engineeringVO) {
//将查询结果类型转换,存入list
List<EngineeringTree> list = engineeringMapper.listObjectsTree(engineeringVO);
return TreeFactory.buildTree(list);
}
总结:
利用Java继承和多态机制封装TreeFactory和TreeNodePlus类,在调用buildTree方法时。只需要根据业务需求写一个list数据对象EngineeringTree实现TreeNodePlus类,最后通过方法类点方法名TreeFactory.buildTree返回树结构数据ArrayList<>(nodeMap.values())。