Java高效构建树形结构——异步加载子节点的实现方案

Java高效构建树形结构的秘密:函数式编程 + 预排序的完美结合

要实现树形结构的异步加载子节点,核心思路是将子节点的获取过程延迟化,通过回调或 CompletableFuture 实现非阻塞加载。以下是具体实现步骤:

一、改造节点模型

在节点对象中增加两个字段,标识子节点是否已加载:

class TbMenuVo {
    // 原有字段...
    private volatile boolean childrenLoaded = false; // 子节点是否已加载
    private volatile boolean loading = false;        // 是否正在加载中(防重复请求)
}

二、定义异步加载接口

新增一个函数式接口,用于按需从外部获取子节点数据(例如从数据库或远程服务):

@FunctionalInterface
public interface AsyncChildrenLoader<T, ID> {
    CompletableFuture<List<T>> loadChildren(ID parentId);
}

三、改造树构建工具类

修改 buildTree 方法,支持异步加载逻辑:

 public static <T, ID> List<T> buildTreeAsync(
    List<T> list,
    Function<T, ID> getId,
    Function<T, ID> getParentId,
    BiConsumer<T, List<T>> setChildren,
    Predicate<T> rootPredicate,
    Comparator<T> comparator,
    AsyncChildrenLoader<T, ID> childrenLoader // 新增异步加载器
) {
    // 预构建父节点映射表(仅处理已知数据)
    Map<ID, List<T>> parentMap = list.stream()
        .filter(item -> getParentId.apply(item) != null)
        .collect(Collectors.groupingBy(getParentId));

    return list.stream()
        .filter(rootPredicate)
        .map(root -> {
            // 初始化时仅设置直接子节点(若有)
            ID rootId = getId.apply(root);
            List<T> immediateChildren = parentMap.getOrDefault(rootId, Collections.emptyList());
            setChildren.accept(root, immediateChildren);
            return root;
        })
        .sorted(comparator)
        .collect(Collectors.toList());
}

四、实现按需加载逻辑

为节点添加异步加载方法,触发时从 AsyncChildrenLoader 获取数据:

public static <T, ID> CompletableFuture<Void> loadChildrenAsync(
    T node,
    Function<T, ID> getId,
    BiConsumer<T, List<T>> setChildren,
    AsyncChildrenLoader<T, ID> childrenLoader,
    Comparator<T> comparator
) {
    ID nodeId = getId.apply(node);
    if (node instanceof TbMenuVo) {
        TbMenuVo menuNode = (TbMenuVo) node;
        if (menuNode.isChildrenLoaded() || menuNode.isLoading()) {
            return CompletableFuture.completedFuture(null);
        }
        menuNode.setLoading(true);
    }

    return childrenLoader.loadChildren(nodeId)
        .thenApply(children -> {
            // 对子节点排序
            if (comparator != null) {
                children.sort(comparator);
            }
            // 设置子节点并标记为已加载
            setChildren.accept(node, children);
            if (node instanceof TbMenuVo) {
                TbMenuVo menuNode = (TbMenuVo) node;
                menuNode.setChildrenLoaded(true);
                menuNode.setLoading(false);
            }
            return null;
        })
        .exceptionally(ex -> {
            // 处理异常(如记录日志)
            if (node instanceof TbMenuVo) {
                ((TbMenuVo) node).setLoading(false);
            }
            return null;
        });
}

五、使用示例
1. 定义异步加载器(模拟数据库查询)
AsyncChildrenLoader<TbMenuVo, Long> loader = parentId -> 
    CompletableFuture.supplyAsync(() -> {
        // 模拟根据parentId查询数据库
        List<TbMenuVo> children = database.queryChildrenByParentId(parentId);
        return children;
    });
2. 构建初始树(仅根节点)
List<TbMenuVo> tree = TreeBuilderOptimized.buildTreeAsync(
    lists, 
    TbMenuVo::getId, 
    TbMenuVo::getParentId,
    TbMenuVo::setChildren,
    item -> item.getParentId() == 0,
    Comparator.comparingInt(TbMenuVo::getSortOrder),
    loader
);
3. 前端触发加载某个节点的子节点
// 当用户点击展开节点时触发
public void onExpandNode(TbMenuVo node) {
    if (!node.isChildrenLoaded()) {
        TreeBuilderOptimized.loadChildrenAsync(
            node, 
            TbMenuVo::getId,
            TbMenuVo::setChildren,
            loader,
            Comparator.comparingInt(TbMenuVo::getSortOrder)
        ).thenRun(() -> {
            // 通知UI更新(例如前端重新渲染)
            refreshUI();
        });
    }
}

六、关键优化点
  1. 防重复请求:通过 loading 标志位避免并发重复加载
  2. 线程安全:使用 volatile 确保状态可见性
  3. 异常处理:捕获异步加载中的异常并恢复状态
  4. 与现有工具兼容:保留同步构建方法,异步方法作为扩展

七、适用场景
  • 大型树形结构数据(如万级以上节点)
  • 需要动态加载的子节点(如文件系统目录树)
  • 前端分批次渲染避免卡顿

通过以上改造,工具类既能保留原有高性能构建能力,又能灵活支持异步加载需求,实现“按需扩展”的树形数据处理!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柚几哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值