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. 内存敏感:使用基于栈的实现,内存使用更可控

相关推荐
AI多Agent协作实战派5 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug
gaolei_eit5 小时前
Java+Ai+vue
java·spring·maven
qq_2518364575 小时前
基于java Web 动漫视频网站毕业论文
java·开发语言·前端
LayZhangStrive5 小时前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
未秃头的程序猿5 小时前
给公司做了个AI客服Agent,用的Spring AI 1.0,3天上线领导拍板了
java·后端·ai编程
曹牧5 小时前
Eclipse 批量文本替换
java·ide·eclipse
程序员清风6 小时前
OpenAI官方发布最新提示词技巧!
java·后端·面试
一只枫林6 小时前
MySQL内、外连接知识点汇总
java·前端·数据库
梅头脑6 小时前
写了3年CRUD,volatile和synchronized的区别还是答不清——直到我画出了这张三性两锁边界图
java
暮暮祈安6 小时前
Celery 新手入门指南
java·数据库·python·flask·httpx