You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将基于CompletableFuture的递归分页查询转为迭代实现以规避栈溢出

如何将基于CompletableFuture的递归分页查询转为迭代实现以规避栈溢出

我完全懂你的顾虑——递归分页的写法虽然清爽,但当数据量极大、分页次数太多时,确实有可能触发栈溢出问题。毕竟每一次递归调用都会在调用栈里留下记录,深度够深就会撑爆栈空间。咱们直接来把这段递归代码改成迭代式的异步实现,既保留CompletableFuture的异步优势,又能彻底避免栈溢出的风险。

首先先理清楚原递归代码的核心逻辑:

  • 从第1页开始查询,每次获取一页数据
  • 如果返回的结果数量小于每页大小,说明已经到最后一页,直接返回当前结果
  • 如果结果数量等于每页大小,就继续查询下一页,然后把当前页和下一页的结果合并,直到触发终止条件

现在我们要把这个递归的调用链,改成迭代式的异步链式调用,核心思路是用累积结果的CompletableFuture加上异步循环控制来代替递归方法调用。

下面是具体的迭代实现代码:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

public class YourDaoClass {
    private static final long PAGE_SIZE = 100; // 假设你的每页大小是100

    // 原有的get方法,这里保留你的实现
    private CompletionStage<List<Entity>> get(List<String> codes, long pageSize, long pageNumber) {
        // 你的数据库分页查询实现,这里省略
        return null;
    }

    public CompletionStage<List<Entity>> getAll(List<String> codes) {
        // 用AtomicReference来保存当前累积结果的CompletableFuture,异步回调中可以修改引用
        AtomicReference<CompletableFuture<List<Entity>>> accumulatedResultRef = new AtomicReference<>(
                CompletableFuture.completedFuture(new ArrayList<>())
        );
        // 用AtomicLong来保存当前页号,支持异步环境下的修改
        AtomicLong currentPage = new AtomicLong(1);

        // 定义一个处理下一页的逻辑,用Runnable来封装迭代步骤
        Runnable fetchNextPage = new Runnable() {
            @Override
            public void run() {
                // 获取当前页的数据
                get(codes, PAGE_SIZE, currentPage.get())
                        .thenCompose(pageResults -> {
                            // 将当前页结果合并到累积结果中
                            CompletableFuture<List<Entity>> updatedAccumulated = accumulatedResultRef.get()
                                    .thenApply(accumulated -> {
                                        List<Entity> combined = new ArrayList<>(accumulated);
                                        combined.addAll(pageResults);
                                        return combined;
                                    });
                            // 更新累积结果的引用
                            accumulatedResultRef.set(updatedAccumulated);
                            // 页号自增
                            currentPage.incrementAndGet();

                            // 判断是否需要继续查询下一页
                            if (pageResults.size() == PAGE_SIZE) {
                                // 还没到最后一页,继续执行下一次迭代
                                this.run();
                            }
                            return updatedAccumulated;
                        });
            }
        };

        // 启动第一次分页查询
        fetchNextPage.run();
        // 返回最终的累积结果Future
        return accumulatedResultRef.get();
    }
}

咱们来拆解一下这个迭代实现的关键点:

  • 用原子类管理状态:因为异步回调是在不同线程中执行的,所以用AtomicReference来保存累积结果的CompletableFuture,用AtomicLong来保存当前页号,确保多线程环境下的状态修改是安全的。
  • 迭代逻辑封装:把“获取下一页、合并结果、判断是否继续”的逻辑封装在一个Runnable中,每次处理完一页后,如果需要继续,就调用自身的run方法——这相当于异步环境下的“循环”,但不会在调用栈中累积,彻底规避栈溢出。
  • 异步结果累积:每次获取到新页的数据后,通过thenApply把新页结果合并到累积结果中,然后更新累积结果的引用,确保后续的迭代能基于最新的累积结果继续处理。

另外还有一种更简洁的实现方式,不需要用原子类,而是利用CompletableFuture的链式调用特性来控制流程:

public CompletionStage<List<Entity>> getAll(List<String> codes) {
    return fetchNextPage(codes, 1L, new ArrayList<>());
}

private CompletionStage<List<Entity>> fetchNextPage(List<String> codes, long currentPage, List<Entity> accumulated) {
    return get(codes, PAGE_SIZE, currentPage)
            .thenCompose(pageResults -> {
                accumulated.addAll(pageResults);
                if (pageResults.size() < PAGE_SIZE) {
                    // 最后一页,返回累积结果
                    return CompletableFuture.completedFuture(accumulated);
                } else {
                    // 继续下一页,这里的调用在异步回调中执行,属于异步调用栈,不会累积同步调用栈深度
                    return fetchNextPage(codes, currentPage + 1, accumulated);
                }
            });
}

这里补充一句:其实原递归代码的栈溢出风险可能并没有你想象的那么大,因为thenCompose的回调是在异步线程中执行的,每次递归调用都是在新的线程栈中,不会累积同步调用栈的深度。但如果你还是想彻底避免任何形式的递归调用,第一种基于Runnable的迭代实现是最稳妥的选择。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 08:58:00