Java | CompletableFuture详解
关注:CodingTechWork
CompletableFuture 概述
介绍
CompletableFuture
是 Java 8
引入的一个非常强大的类,属于 java.util.concurrent
包。它是用于异步编程的一个工具,可以帮助我们更方便地处理并发任务。与传统的线程池或 Future
对比,CompletableFuture
提供了更多灵活性和组合功能,使得异步编程更加简单和易于维护。
CompletableFuture
主要用于异步操作和组合多个异步任务。它可以通过执行非阻塞的操作来避免阻塞主线程,从而提高程序的性能和响应速度。
CompletableFuture
实现了 Future
和 CompletionStage
接口。
优势
相比传统的 Future 接口,具有以下核心优势:
- 支持非阻塞的异步操作
- 提供链式调用和组合操作的能力
- 内置完善的异常处理机制
- 支持函数式编程风格
与传统 Future 对比
特性 | Future | CompletableFuture |
---|---|---|
结果获取 | 阻塞 get() 方法 | 支持回调通知 |
链式调用 | 不支持 | 支持多级流水线处理 |
异常处理 | 需要 try-catch | 内置异常传播机制 |
组合操作 | 手动实现 | 提供多种组合方法 |
手动完成 | 不支持 | 支持 complete() 方法 |
核心方法
supplyAsync()
用于执行一个异步任务并返回一个结果。它接受一个 Supplier
,并在后台线程执行该任务。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {return 42;
});
runAsync()
用于执行一个异步任务,但不返回结果。它接受一个Runnable
,并在后台线程执行该任务。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {System.out.println("Task executed");
});
thenApply()
用于将前一个计算的结果转换为另一个结果。它接受一个Function
,该函数作用于先前计算的结果,并返回一个新的CompletableFuture
。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5).thenApply(result -> result * 2);
thenAccept()
用于处理前一个计算结果并返回Void
。它接受一个Consumer
,用于对先前的结果执行操作。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> 5).thenAccept(result -> System.out.println(result));
thenCombine()
用于将两个CompletableFuture
的结果结合成一个新的结果。它接受两个CompletableFuture
和一个BiFunction
。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 10);CompletableFuture<Integer> combined = future1.thenCombine(future2, (result1, result2) -> result1 + result2);
thenCompose()
用于将两个异步操作串联起来,第二个任务依赖第一个任务的结果。它接受一个Function
,返回一个新的CompletableFuture
。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5).thenCompose(result -> CompletableFuture.supplyAsync(() -> result * 2));
allOf()
用于组合多个CompletableFuture
,并等待它们全部完成。返回一个新的 CompletableFuture
,它完成时表示所有的CompletableFuture
都已完成。
CompletableFuture<Void> future1 = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> 10);CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2);
anyOf()
用于组合多个CompletableFuture
,并等待任意一个任务完成。返回一个新的 CompletableFuture
,该CompletableFuture
完成时表示至少有一个任务已完成。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 5);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 10);CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2);
exceptionally()
用于在异步任务执行过程中发生异常时处理异常。它接受一个Function
,如果任务执行失败(抛出异常),则返回一个备用值。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {if (true) throw new RuntimeException("Error");return 5;})// 处理异常,返回-1.exceptionally(ex -> -1);
whenComplete()
用于在任务完成后进行额外操作,不论任务是正常完成还是异常完成。它接受一个 BiConsumer
,用于处理结果和异常。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5).whenComplete((result, ex) -> {if (ex == null) {System.out.println("Completed with result: " + result);} else {System.out.println("Completed with exception: " + ex.getMessage());}});
supplyAsync()和runAsync() 区别
CompletableFuture.supplyAsync() 和 CompletableFuture.runAsync() 都是用来异步执行任务的,但它们之间有一些关键区别,主要体现在是否返回结果以及如何处理任务:
返回值
supplyAsync()功能
:该方法接受一个Supplier
函数,Supplier
是一个能够返回结果的函数。它会异步执行任务并返回一个CompletableFuture<T>
,这个CompletableFuture
可以通过get()
等方法获取任务的返回值。supplyAsync()适用场景
:当你需要执行一个任务,并且希望获取这个任务的结果时,使用supplyAsync()
。runAsync()功能
:该方法接受一个Runnable
函数,Runnable
是一个没有返回值的任务。它会异步执行任务,但不会返回任何结果,因此返回的是一个CompletableFuture<Void>
,意味着你只能知道任务是否完成,但无法直接获取任务的结果。runAsync()适用场景
:当你只需要执行任务,但不关心返回结果时,使用runAsync()
。
任务执行
supplyAsync()
:异步任务执行后会返回一个值,通常使用 thenApply()、thenAccept() 等方法来处理返回值。runAsync()
:执行的是没有返回值的任务,通常用 thenRun() 来处理任务完成后的操作。
使用场景
supplyAsync()
:适用于有返回值的任务,例如你需要异步计算某个结果。runAsync()
:适用于没有返回值的任务,例如日志记录、状态更新等。
技术原理
创建异步任务
// 使用默认 ForkJoinPool
CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Result");// 指定自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {// 业务逻辑return "Custom Result";
}, executor);
结果处理
同步回调
// thenApply: 同步转换结果
cf.thenApply(s -> s + " processed").thenAccept(System.out::println);// thenAccept: 同步消费结果
cf.thenAccept(result -> {System.out.println("Received: " + result);
});
异步回调
// thenApplyAsync: 异步转换
cf.thenApplyAsync(s -> {// 在 ForkJoinPool 异步执行return s.toUpperCase();
});// 指定自定义线程池
cf.thenAcceptAsync(result -> {System.out.println("Async processing");
}, executor);
组合操作
串行组合
// 查询用户信息,然后根据用户ID查询订单信息(串行执行)
CompletableFuture<String> cf = queryUserInfo()// 使用 thenCompose 实现串行组合.thenCompose(user -> queryOrder(user.getId()));
并行组合
// 并行查询服务A和服务B
// 查询服务A
CompletableFuture<String> cf1 = queryServiceA();
// 查询服务B
CompletableFuture<String> cf2 = queryServiceB(); // 合并两个结果(并行执行,结果合并)
CompletableFuture<String> combined = cf1// 使用 thenCombine 合并两个任务的结果.thenCombine(cf2, (res1, res2) -> res1 + " & " + res2); // 任意一个完成即返回(并行执行,取最先完成的结果)
// 使用 anyOf 实现快速返回
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(cf1, cf2);
异常处理
// 异常处理:捕获异常并返回默认值
cf.exceptionally(ex -> {// 打印异常信息System.err.println("Error: " + ex.getMessage()); // 返回默认值return "default value";
});// 异常处理:捕获异常并返回 fallback 值
cf.handle((result, ex) -> {// 如果有异常if (ex != null) { // 返回 fallback 值return "fallback"; }// 否则返回正常结果return result;
});
超时控制
// 超时控制:如果任务在指定时间内未完成,返回默认值
// 1秒后未完成则返回 "default"
cf.completeOnTimeout("default", 1, TimeUnit.SECONDS);
典型应用场景
并行任务处理
// 并行处理多个请求
List<CompletableFuture<String>> futures = requests.stream().map(request -> // 对每个请求异步处理CompletableFuture.supplyAsync(() -> process(request), executor)) // 收集所有任务.collect(Collectors.toList()); // 等待所有任务完成
CompletableFuture<Void> allDone = CompletableFuture// 使用 allOf 等待所有任务完成.allOf(futures.toArray(new CompletableFuture[0]));
服务调用编排
// 服务调用编排:依次执行多个异步任务
CompletableFuture<Order> orderFuture = // 获取用户信息getUserProfile() // 验证地址.thenCompose(user -> validateAddress(user)) // 创建订单.thenCompose(address -> createOrder(address)) // 异常时创建备用订单.exceptionally(ex -> createFallbackOrder());
异步结果聚合
// 异步结果聚合:合并两个异步任务的结果
CompletableFuture<Integer> total = // 任务1:返回10CompletableFuture.supplyAsync(() -> 10) // 任务2:返回20,合并结果为30.thenCombine(CompletableFuture.supplyAsync(() -> 20), Integer::sum);
demo:电商订单处理
场景需求
- 并行查询用户信息和商品库存
- 验证地址有效性
- 组合结果创建订单
- 记录操作日志
实现代码
import java.util.concurrent.*;public class OrderService {private final ExecutorService executorService;// 构造函数接受线程池大小参数public OrderService(int poolSize) {// 创建一个自定义线程池,大小为 poolSizethis.executorService = Executors.newFixedThreadPool(poolSize);System.out.println("Thread pool initialized with size: " + poolSize);}// 线程池大小配置方法public void setThreadPoolSize(int poolSize) {// 关闭现有线程池并创建一个新的线程池shutdown(); // 关闭旧线程池this.executorService = Executors.newFixedThreadPool(poolSize); // 创建新的线程池System.out.println("Thread pool resized to: " + poolSize);}// 获取当前线程池大小public int getThreadPoolSize() {if (executorService instanceof ThreadPoolExecutor) {return ((ThreadPoolExecutor) executorService).getCorePoolSize();}return -1; // 如果没有获取到线程池大小,则返回 -1}public CompletableFuture<OrderResult> createOrderAsync(OrderRequest request) {// 并行查询用户信息和库存信息CompletableFuture<UserInfo> userFuture = queryUserAsync(request.getUserId());CompletableFuture<Inventory> inventoryFuture = queryInventoryAsync(request.getSkuId());// 使用 thenCombine 合并两个异步结果return userFuture.thenCombine(inventoryFuture, (user, inventory) -> {// 验证用户地址validateAddress(user.getAddress());// 检查库存是否足够checkInventory(inventory);// 根据用户信息、库存信息和订单请求生成订单return generateOrder(user, inventory, request);})// 使用 thenCompose 对生成的订单进行进一步处理.thenCompose(order -> {// 异步保存订单return saveOrderAsync(order)// 保存成功后,发送通知.thenApply(savedOrder -> sendNotification(savedOrder));})// 处理最终结果,或者处理异常.handle((result, ex) -> {// 如果出现异常,记录错误并返回失败的结果if (ex != null) {log.error("Order failed", ex);return OrderResult.failure(ex.getMessage());}// 如果成功,返回成功的结果return OrderResult.success(result);});}// 异步查询用户信息,使用自定义线程池private CompletableFuture<UserInfo> queryUserAsync(String userId) {return CompletableFuture.supplyAsync(() -> userService.getUser(userId), executorService);}// 异步查询库存信息,使用自定义线程池private CompletableFuture<Inventory> queryInventoryAsync(String skuId) {return CompletableFuture.supplyAsync(() -> inventoryService.getStock(skuId), executorService);}// 验证用户地址是否合法private void validateAddress(String address) {if (address == null || address.isEmpty()) {throw new IllegalArgumentException("Address is invalid.");}// 进一步的地址验证逻辑}// 检查库存是否足够private void checkInventory(Inventory inventory) {if (inventory == null || inventory.getStock() <= 0) {throw new IllegalArgumentException("Insufficient inventory.");}}// 根据用户和库存信息生成订单private Order generateOrder(UserInfo user, Inventory inventory, OrderRequest request) {Order order = new Order();order.setUserId(user.getUserId());order.setSkuId(request.getSkuId());order.setQuantity(request.getQuantity());order.setTotalPrice(inventory.getPrice() * request.getQuantity());order.setShippingAddress(user.getAddress());order.setStatus(OrderStatus.PENDING);return order;}// 异步保存订单,使用自定义线程池private CompletableFuture<Order> saveOrderAsync(Order order) {return CompletableFuture.supplyAsync(() -> orderService.saveOrder(order), executorService);}// 发送订单通知private String sendNotification(Order order) {notificationService.sendOrderConfirmation(order);return "Order placed successfully!";}// 关闭线程池public void shutdown() {if (executorService != null && !executorService.isShutdown()) {executorService.shutdown();System.out.println("Thread pool shut down gracefully.");}}// 强制关闭线程池public void shutdownNow() {if (executorService != null && !executorService.isShutdown()) {executorService.shutdownNow();System.out.println("Thread pool shut down immediately.");}}// 线程池是否已关闭public boolean isShutdown() {return executorService.isShutdown();}// 主方法示例public static void main(String[] args) {// 初始化 OrderService 并传入线程池大小OrderService orderService = new OrderService(5);// 获取并输出当前线程池大小int poolSize = orderService.getThreadPoolSize();System.out.println("Current thread pool size: " + poolSize);// 假设创建一个订单请求对象OrderRequest orderRequest = new OrderRequest("user123", "sku456", 2);orderService.createOrderAsync(orderRequest).thenAccept(result -> {System.out.println(result.getMessage());});// 动态调整线程池大小orderService.setThreadPoolSize(10);System.out.println("Updated thread pool size: " + orderService.getThreadPoolSize());// 关闭线程池orderService.shutdown();}
}
输出结果
Thread pool initialized with size: 5
Current thread pool size: 5
Order placed successfully!
Thread pool resized to: 10
Updated thread pool size: 10
Thread pool shut down gracefully.
最佳实践与注意事项
线程池选择策略
CPU密集型
任务使用有界线程池IO密集型
任务使用缓存线程池- 避免混合使用不同任务类型
资源管理
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {CompletableFuture.runAsync(() -> task(), executor);
}
调试技巧
- 使用 thenApplyAsync 添加日志点
- 包装异步操作添加跟踪ID
cf.thenApplyAsync(result -> {log.debug("[Trace-{}] Step completed", traceId);return result;
});
性能优化
- 避免过度嵌套回调
- 及时关闭自定义线程池
- 使用
CompletableFuture#join()
谨慎
总结
CompletableFuture
为 Java 异步编程提供了强大支持,特别适用于:
- 需要编排多个异步操作的场景
- 实现非阻塞的响应式系统
- 需要精细控制任务执行顺序和依赖关系
- 构建高并发、低延迟的服务
相关文章:

Java | CompletableFuture详解
关注:CodingTechWork CompletableFuture 概述 介绍 CompletableFuture是 Java 8 引入的一个非常强大的类,属于 java.util.concurrent 包。它是用于异步编程的一个工具,可以帮助我们更方便地处理并发任务。与传统的线程池或 Future 对比&…...

【背包问题】二维费用的背包问题
目录 二维费用的背包问题详解 总结: 空间优化: 1. 状态定义 2. 状态转移方程 3. 初始化 4. 遍历顺序 5. 时间复杂度 例题 1,一和零 2,盈利计划 二维费用的背包问题详解 前面讲到的01背包中,对物品的限定条件…...

Golang 并发机制-5:详解syn包同步原语
并发性是现代软件开发的一个基本方面,Go(也称为Golang)为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中,我们将概述“sync”包,并深入研究其最重要的同步原语之一…...

实验六 项目二 简易信号发生器的设计与实现 (HEU)
声明:代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog(或VHDL)、图形描述方式、IP核,结合数字系统设计方法,在Quartus开发环境下ÿ…...

如何用微信小程序写春联
生活没有模板,只需心灯一盏。 如果笑能让你释然,那就开怀一笑;如果哭能让你减压,那就让泪水流下来。如果沉默是金,那就不用解释;如果放下能更好地前行,就别再扛着。 一、引入 Vant UI 1、通过 npm 安装 npm i @vant/weapp -S --production 2、修改 app.json …...

LabVIEW无人机航线控制系统
介绍了一种无人机航线控制系统,该系统利用LabVIEW软件与MPU6050九轴传感器相结合,实现无人机飞行高度、速度、俯仰角和滚动角的实时监控。系统通过虚拟仪器技术,有效实现了数据的采集、处理及回放,极大提高了无人机航线的控制精度…...

C++哈希表深度解析:从原理到实现,全面掌握高效键值对存储
目录 一、核心组件与原理 1. 哈希函数(Hash Function) 2. 冲突解决(Collision Resolution) 3. 负载因子(Load Factor)与扩容 二、C实现:std::unordered_map 1. 模板参数 2. 关键操作与复…...

Vue.js组件开发-实现字母向上浮动
使用Vue实现字母向上浮动的效果 实现步骤 创建Vue项目:使用Vue CLI来创建一个新的Vue项目。定义组件结构:在组件的模板中,定义包含字母的元素。添加样式:使用CSS动画来实现字母向上浮动的效果。绑定动画类:在Vue组件…...

自研有限元软件与ANSYS精度对比-Bar2D2Node二维杆单元模型-四连杆实例
目录 1、四连杆工程实例以及手算求解 2、四连杆的自研有限元软件求解 2.1、选择单元类型 2.2、导入四连杆工程 2.3、节点坐标定义 2.4、单元连接关系、材料定义 2.5、约束定义 2.6、外载定义 2.7、矩阵求解 2.8、变形云图展示 2.9、节点位移 2.10、单元应力 2.11、…...

04树 + 堆 + 优先队列 + 图(D1_树(D11_伸展树))
目录 一、基本介绍 二、伸展操作 1. 左右情况的伸展 2. 左左情况的伸展 3. 右左情况的伸展 4. 右右情况的伸展 三、其它操作 1. 插入 2. 删除 四、代码实现 一、基本介绍 伸展树是一种二叉搜索树,伸展树也是一种平衡树,不过伸展树并不像AVL树那…...

c语言练习题【数据类型、递归、双向链表快速排序】
练习1:数据类型 请写出以下几个数据的数据类型 整数 a a 的地址 存放a的数组 b 存放a的地址的数组 b的地址 c的地址 指向 printf 函数的指针 d 存放 d的数组 整数 a 的类型 数据类型是 int a 的地址 数据类型是 int*(指向 int 类型的指针) …...

SliverAppBar的功能和用法
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverGrid组件相关的内容,本章回中将介绍SliverAppBar组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverAppBar和普通的AppBar类似,它们的…...

五、定时器实现呼吸灯
5.1 定时器与计数器简介 定时器是一种通过对内部时钟脉冲计数来测量时间间隔的模块。它的核心是一个递增或递减的寄存器(计数器值)。如果系统时钟为 1 MHz,定时器每 1 μs 计数一次。 计数器是一种对外部事件(如脉冲信号ÿ…...

Elasticsearch的索引生命周期管理
目录 说明零、参考一、ILM的基本概念二、ILM的实践步骤Elasticsearch ILM策略中的“最小年龄”是如何计算的?如何监控和调整Elasticsearch ILM策略的性能? 1. **监控性能**使用/_cat/thread_pool API基本请求格式请求特定线程池的信息响应内容 2. **调整…...

【大模型理论篇】最近大火的DeepSeek-R1初探系列1
1. 背景介绍 这一整个春节,被DeepSeek-R1刷屏。各种铺天盖地的新闻以及老板发的相关信息,着实感受到DeepSeek-R1在国外出圈的震撼。 DeepSeek推出了新的推理模型:DeepSeek-R1-Zero 和 DeepSeek-R1。DeepSeek-R1-Zero 是一个在没有经过监督微调…...

【数据结构】(4) 线性表 List
一、什么是线性表 线性表就是 n 个相同类型元素的有限序列,每一个元素只有一个前驱和后继(除了第一个和最后一个元素)。 数据结构中,常见的线性表有:顺序表、链表、栈、队列。 二、什么是 List List 是 Java 中的线性…...

【C++ STL】vector容器详解:从入门到精通
【C STL】vector容器详解:从入门到精通 摘要:本文深入讲解C STL中vector容器的使用方法,涵盖常用函数、代码示例及注意事项,助你快速掌握动态数组的核心操作! 一、vector概述 vector是C标准模板库(STL&am…...

OpenAI推出Deep Research带给我们怎样的启示
OpenAI 又发新产品了,这次是面向深度研究领域的智能体产品 ——「Deep Research」,貌似被逼无奈的节奏… 在技术方面,Deep Research搭载了优化后o3模型并通过端到端强化学习在多个领域的复杂浏览和推理任务上进行了训练。因没有更多的技术暴露…...

洛谷[USACO08DEC] Patting Heads S
题目传送门 题目难度:普及/提高一 题面翻译 今天是贝茜的生日,为了庆祝自己的生日,贝茜邀你来玩一个游戏。 贝茜让 N N N ( 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1≤N≤105) 头奶牛坐成一个圈。除了 1 1 1 号与 N N N 号奶牛外࿰…...

CSS 溢出内容处理:从基础到实战
CSS 溢出内容处理:从基础到实战 1. 什么是溢出?示例代码:默认溢出行为 2. 使用 overflow 属性控制溢出2.1 使用 overflow: hidden 裁剪内容示例代码:裁剪溢出内容 2.2 使用 overflow: scroll 显示滚动条示例代码:显示滚…...

Spring Boot项目如何使用MyBatis实现分页查询
写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭&#x…...

飞行汽车中的无刷外转子电机、人形机器人中的无框力矩电机技术解析与应用
重点:无刷外转子电机与无框力矩电机:技术解析与应用对比 在现代工业自动化和精密机械领域,无刷电机因其高效、低噪音和高可靠性而备受青睐。其中,无刷外转子电机和无框力矩电机更是以其独特的结构和性能特点,成为众多应用场景中的…...

FreeRTOS学习 --- 队列集
队列集简介 一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集 ! 作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让…...

【R语言】R语言安装包的相关操作
一、管理R语言安装包 1、安装R包 install.packages() 2、查看已安装的R包 installed.packages() 3、更新R包 update.packages() 4、卸载R包 remove.packages() 二、加载R语言安装包 打开R语言时,基础包(base包)会自动被加载到内存中…...

15.[前端开发]Day15-HTML+CSS阶段练习(网易云音乐四)
完整代码 01_网易云-header <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"wid…...

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户登录
🧸安清h:个人主页 🎥个人专栏:【Spring篇】【计算机网络】【Mybatis篇】 🚦作者简介:一个有趣爱睡觉的intp,期待和更多人分享自己所学知识的真诚大学生。 目录 🎯1.登录-持久层 &…...

测试方案和测试计划相同点和不同点
在软件测试领域,测试方案与测试计划皆为举足轻重的关键文档,尽管它们有着紧密的关联,但在目的与内容层面存在着显著的差异。相同点: 1.共同目标:测试方案和测试计划的核心目标高度一致,均致力于保障软件的…...

c++提取矩形区域图像的梯度并拟合直线
c提取旋转矩形区域的边缘最强梯度点,并拟合直线 #include <opencv2/opencv.hpp> #include <iostream> #include <vector>using namespace cv; using namespace std;int main() {// 加载图像Mat img imread("image.jpg", IMREAD_GRAYS…...

Unity Shader Graph 2D - 角色身体电流覆盖效果
在游戏中,通常会有游戏角色受到“电击”的效果,此时游戏角色身体上会覆盖有电流,该效果能表明游戏角色的当前状态,让玩家能够获得更直观更好的体验。 那么如何实现呢 首先创建一个ShaderGraph文件,命名为Current,再创建对应的材质球M_Current。 基础的资源显示 老规矩,…...

【LLM-agent】(task4)搜索引擎Agent
note 新增工具:搜索引擎Agent 文章目录 note一、搜索引擎AgentReference 一、搜索引擎Agent import os from dotenv import load_dotenv# 加载环境变量 load_dotenv() # 初始化变量 base_url None chat_model None api_key None# 使用with语句打开文件…...