01-线上问题处理-树形结构拼接

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 非递归实现相比递归实现有以下优势:

  1. 避免栈溢出:递归实现在处理深度很大的树时可能导致栈溢出,而非递归实现没有这个问题

  2. 性能更好:非递归实现通常比递归实现有更好的性能,特别是对于大数据集

  3. 更易调试:非递归实现的执行流程更线性,更容易调试和理解

  4. 内存控制:非递归实现可以更好地控制内存使用,特别是在使用迭代器或队列时

  5. 可中断性:非递归实现可以更容易地添加中断条件或超时机制

4 使用建议

  1. 小规模数据:可以使用简单的递归实现,代码更简洁

  2. 大规模数据:推荐使用非递归实现,避免栈溢出风险

  3. 性能关键:使用高效的单次遍历实现,性能最佳

  4. 内存敏感:使用基于栈的实现,内存使用更可控

相关推荐
码农小河669 小时前
基于SSM+JSP的高校专业信息管理系统【附源码+数据库】
java·开发语言·数据库
风若飞11 小时前
Spring Ai 入门遇到问题1
java·后端·spring
Nuanyt12 小时前
LeetCode Hot 100 链表专题(五)· 2 题(Java)
java·算法·leetcode·链表
黑马程序员毕设12 小时前
基于 Java 的餐饮点餐系统设计与应用报告
java·开发语言
SL_staff12 小时前
从文档工具到知识中枢:基于JVS数字底座构建企业级统一内容管理平台的技术实践
java·开源·全栈
SL_staff12 小时前
从‘连得上’到‘管得好’:JVS-IOT双引擎驱动设备运维闭环
java·物联网·产品
w_zero_one15 小时前
递归算法(一)
java·开发语言·算法
SL_staff15 小时前
私有化低代码不是减配版开发:JVS双模式建模如何重建IT交付信任
java·低代码·spring cloud
墨着染霜华16 小时前
Redocly 生成 OpenAPI 接口文档踩坑实录|Node 版本兼容 + 接口地址错误问题解决
java
文丑颜不良啊16 小时前
LeetCode1881~1990题目解析
java·leetcode