1 递归方式实现
1.1 代码
步骤一:一次性获取到所有的数据tisManualCatalogPOS
步骤二:获取到一级节点(parentId为null)
步骤三:递归向下拼接
树的深度很深,导致堆栈越多,可能
生产8K的数据能很明显的看到接口响应慢,<没打印日志>
private List<ManualTreeVO> buildTree(List<TisManualCatalogPO> tisManualCatalogPOS) {
List<ManualTreeVO> treeVOList = Lists.newArrayList();
if (CollectionUtils.isEmpty(tisManualCatalogPOS)) {
return treeVOList;
}
List<ManualTreeVO> dataList = tisManualCatalogPOS.stream().map(x -> {
ManualTreeVO bean = new ManualTreeVO();
bean.setId(x.getId());
bean.setName(x.getName());
bean.setSort(x.getSort());
bean.setType(x.getType());
bean.setCode(x.getCode());
bean.setParentId(x.getParentId());
String filePath = x.getFilePath();
// 不包含则拼前缀
if (StringUtils.isNotEmpty(filePath) && !filePath.contains(filePathPrefix)) {
filePath = filePathPrefix + filePath;
}
bean.setFilePath(filePath);
return bean;
}).collect(Collectors.toList());
// 拿到所有的一级节点
List<ManualTreeVO> firstCatalogList = dataList.stream()
.filter(x -> Objects.isNull(x.getParentId()))
.sorted(Comparator.comparing(ManualTreeVO::getSort))
.collect(Collectors.toList());
for (ManualTreeVO data : firstCatalogList) {
// 构建树
doBuildTree(dataList, data);
treeVOList.add(data);
}
return treeVOList;
}
private void doBuildTree(List<ManualTreeVO> hoursCatalogList, ManualTreeVO treeVO) {
// 下一级
List<ManualTreeVO> childrenList = getChildrenList(hoursCatalogList, treeVO);
treeVO.setChildrenList(childrenList);
for (ManualTreeVO children : childrenList) {
// 有下级
if (!CollectionUtils.isEmpty(getChildrenList(hoursCatalogList, children))) {
doBuildTree(hoursCatalogList, children);
}
}
}
private List<ManualTreeVO> getChildrenList(List<ManualTreeVO> hoursCatalogList, ManualTreeVO treeVO) {
// 下一级
List<ManualTreeVO> nextList = hoursCatalogList.stream().filter(x -> Objects.equals(treeVO.getCode(), x.getParentId()))
.sorted(Comparator.comparing(ManualTreeVO::getSort))
.collect(Collectors.toList());
return nextList;
}
1.2 性能
2 非递归方式实现(推荐)
2.1 代码
使用Map,哈希的方式用空间换时间,一次遍历即可拼接成树
public List<ManualTreeVO> buildTreeNew(String uuid, List<TisManualCatalogPO> tisManualCatalogPOS) {
log.info("手册目录-拼接目录 uuid {} {}", uuid, CollectionUtils.isNotEmpty(tisManualCatalogPOS) ? tisManualCatalogPOS.size() : 0);
List<ManualTreeVO> roots = Lists.newArrayList();
Map<String, ManualTreeVO> nodeMap = new HashMap<>();
List<ManualTreeVO> dataList = tisManualCatalogPOS.stream().map(x -> {
ManualTreeVO bean = new ManualTreeVO();
bean.setId(x.getId());
bean.setName(x.getName());
bean.setSort(x.getSort());
bean.setType(x.getType());
bean.setCode(x.getCode());
bean.setParentId(x.getParentId());
String filePath = x.getFilePath();
// 不包含则拼前缀
if (StringUtils.isNotEmpty(filePath) && !filePath.contains(filePathPrefix)) {
filePath = filePathPrefix + filePath;
}
bean.setFilePath(filePath);
return bean;
}).collect(Collectors.toList());
// 将所有节点存入映射表
for (ManualTreeVO node : dataList) {
nodeMap.put(node.getCode(), node);
}
// 构建树结构
for (ManualTreeVO node : dataList) {
String parentId = node.getParentId();
if (parentId == null || parentId.isEmpty()) {
roots.add(node);
} else {
ManualTreeVO parent = nodeMap.get(parentId);
if (parent != null) {
parent.addChild(node);
} else {
// 如果父节点不存在,将其作为根节点
roots.add(node);
}
}
}
log.info("手册目录-拼接目录 uuid end {}", uuid);
return roots;
}
2.2 性能
stg日志:3662条数据拼接成树花费时间
3 优劣对比
使用 Map 非递归实现相比递归实现有以下优势:
避免栈溢出:递归实现在处理深度很大的树时可能导致栈溢出,而非递归实现没有这个问题
性能更好:非递归实现通常比递归实现有更好的性能,特别是对于大数据集
更易调试:非递归实现的执行流程更线性,更容易调试和理解
内存控制:非递归实现可以更好地控制内存使用,特别是在使用迭代器或队列时
可中断性:非递归实现可以更容易地添加中断条件或超时机制
4 使用建议
小规模数据:可以使用简单的递归实现,代码更简洁
大规模数据:推荐使用非递归实现,避免栈溢出风险
性能关键:使用高效的单次遍历实现,性能最佳
内存敏感:使用基于栈的实现,内存使用更可控