深入理解Java CompletableFuture多线程编排的最佳实践
1. 引言
1.1 多线程编排的必要性
在现代应用程序中,尤其是涉及网络请求、大数据处理或高并发场景时,多线程编排变得尤为重要。传统的顺序执行方式可能导致性能瓶颈,增加响应时间,从而影响用户体验和系统效率。通过多线程编排,我们可以并行处理多个任务,提高程序的响应能力和整体性能。这样的编排不仅可以优化资源使用,还能实现更复杂的任务流程,如任务依赖和异步执行。
1.2 CompletableFuture的背景和用途
CompletableFuture是Java 8引入的一个强大工具,它提供了一种灵活的方式来处理异步编程和多线程任务的组合。与传统的Future接口相比,CompletableFuture不仅支持异步计算,还允许我们轻松地创建复杂的任务链和处理回调。这使得我们能够更加直观和简洁地编排异步任务,提高代码的可读性和维护性。
通过使用CompletableFuture,我们可以实现诸如并行计算、任务组合、异常处理和超时控制等功能,使得多线程编排变得更加简单和高效。因此,CompletableFuture在现代Java开发中得到了广泛应用,尤其是在构建微服务架构、处理REST API和进行数据处理时。
2. CompletableFuture基础
2.1 什么是CompletableFuture?
CompletableFuture是Java 8引入的一个类,属于java.util.concurrent包。它代表一个可以在未来某个时间点完成的异步计算,允许开发者以非阻塞的方式处理任务。通过CompletableFuture,我们可以轻松地进行异步编程,处理复杂的任务依赖关系,并在任务完成时触发回调。
2.2 主要特性与优势
- 异步执行:支持在后台线程中执行任务,避免阻塞主线程,提高响应性。
- 任务组合:可以通过链式调用将多个CompletableFuture组合在一起,形成复杂的任务流。
- 异常处理:提供了丰富的异常处理机制,可以优雅地捕获和处理任务中的异常。
- 灵活性:支持多种回调方式,如
thenApply、thenAccept等,适应不同的编程需求。
2.3 与传统线程的比较
- 简洁性:CompletableFuture提供了更简洁的API,减少了回调地狱的问题,代码更加清晰。
- 可组合性:传统的线程需要手动管理任务间的依赖,而CompletableFuture可以轻松地组合多个任务。
- 错误处理:CompletableFuture内置了异常处理机制,相比传统的线程,能够更优雅地处理异常情况。
- 资源管理:CompletableFuture允许使用共享的线程池,优化资源利用,而传统线程通常需要单独管理线程生命周期。
3. 创建CompletableFuture
3.1 使用静态工厂方法创建实例
CompletableFuture提供了多种静态工厂方法来创建实例:
CompletableFuture.supplyAsync(Supplier<U> supplier):异步执行并返回结果的任务,适合需要返回值的场景。CompletableFuture.runAsync(Runnable runnable):异步执行不返回值的任务,适合只需执行操作的场景。
例如:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 模拟耗时操作return "Hello, CompletableFuture!";
});
3.2 完成(complete)和取消(cancel)操作
- 完成操作:可以手动完成CompletableFuture,通过调用
complete(V value)方法,强制设置其结果。
CompletableFuture<String> future = new CompletableFuture<>();
future.complete("Manual Completion!");
- 取消操作:可以通过
cancel(boolean mayInterruptIfRunning)方法取消任务,mayInterruptIfRunning参数决定是否中断正在执行的任务。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {// 模拟耗时操作
}).cancel(true); // 取消任务
这些操作使得CompletableFuture在需要灵活控制任务执行时非常有效。
4. 多线程编排示例
4.1 基本的异步任务执行
使用CompletableFuture,我们可以轻松地执行异步任务。以下是如何使用supplyAsync和runAsync方法的示例。
// 使用supplyAsync执行返回值的异步任务
CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {// 模拟耗时操作return "Hello from supplyAsync!";
});// 使用runAsync执行不返回值的异步任务
CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {// 模拟耗时操作System.out.println("Running an asynchronous task from runAsync!");
});
4.2 supplyAsync和runAsync的用法
supplyAsync用于执行需要返回结果的任务,返回一个CompletableFuture对象。runAsync用于执行不需要返回结果的任务,返回一个CompletableFuture对象。
4.3 任务链的构建
CompletableFuture支持通过链式调用构建任务链,以便在前一个任务完成后自动执行后续任务。
CompletableFuture<String> chainedFuture = CompletableFuture.supplyAsync(() -> {return "Initial Result";
}).thenApply(result -> {// 在上一个任务的结果基础上进行处理return result + " - Processed";
}).thenAccept(finalResult -> {// 接收处理后的结果System.out.println(finalResult);
});
4.4 使用thenApply、thenAccept和thenRun
- thenApply:用于将上一个任务的结果转换为新的结果。
- thenAccept:用于消费上一个任务的结果,而不返回新结果。
- thenRun:用于在上一个任务完成后执行另一个不需要结果的操作。
CompletableFuture<Integer> computationFuture = CompletableFuture.supplyAsync(() -> {// 模拟计算任务return 42;
}).thenApply(result -> {// 转换结果return result * 2;
}).thenAccept(finalResult -> {// 输出最终结果System.out.println("Final Result: " + finalResult);
}).thenRun(() -> {// 完成后的操作System.out.println("All tasks are done!");
});
这些方法使得CompletableFuture的多线程编排变得简单而强大,能够方便地处理复杂的异步任务流程。
5. 错误处理
在使用CompletableFuture进行异步编程时,处理异常是一个重要的方面。CompletableFuture提供了几种处理异常的机制,主要包括exceptionally和handle方法。
5.1 使用exceptionally处理异常
exceptionally方法允许我们在任务执行过程中捕获异常并提供备用结果。它接收一个函数作为参数,这个函数会在任务执行失败时被调用。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {// 模拟可能抛出异常的操作if (true) {throw new RuntimeException("Something went wrong!");}return "Successful Result";
}).exceptionally(ex -> {// 处理异常并返回备用结果System.err.println("Error: " + ex.getMessage());return "Default Result";
}).thenAccept(result -> {// 输出结果System.out.println("Result: " + result);
});
在这个示例中,如果SupplyAsync中抛出异常,exceptionally会捕获该异常并返回一个默认结果。
5.2 使用handle方法的灵活性
handle方法更为灵活,它既可以处理正常结果,也可以处理异常。它接收一个BiFunction参数,第一个参数是正常结果,第二个参数是异常(如果有的话)。这样,你可以在一个方法中同时处理成功和失败的情况。
CompletableFuture<String> futureWithHandle = CompletableFuture.supplyAsync(() -> {// 模拟可能抛出异常的操作if (true) {throw new RuntimeException("Something went wrong!");}return "Successful Result";
}).handle((result, ex) -> {if (ex != null) {// 处理异常System.err.println("Error: " + ex.getMessage());return "Handled Default Result";}// 返回正常结果return result;
}).thenAccept(finalResult -> {// 输出最终结果System.out.println("Final Result: " + finalResult);
});
在这个例子中,handle方法能够根据任务的执行结果或异常情况返回不同的结果,使得错误处理更加灵活。
通过使用exceptionally和handle,我们可以优雅地处理异步任务中的异常,提升代码的健壮性和可维护性。
6. 组合多个CompletableFuture
在实际应用中,常常需要同时执行多个异步任务,并将它们的结果组合在一起。CompletableFuture提供了thenCombine和thenCompose方法来实现这一点。
6.1 使用thenCombine进行任务组合
thenCombine用于组合两个CompletableFuture的结果。它会在两个任务都完成后执行一个合并操作,接收两个结果作为参数。
应用场景:例如,需要同时获取用户信息和用户的订单信息,然后将它们合并。
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> {// 获取用户信息return new User("John", 30);
});CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> {// 获取用户订单信息return new Order("Order123", 100);
});CompletableFuture<UserOrderInfo> combinedFuture = userFuture.thenCombine(orderFuture, (user, order) -> {// 合并用户和订单信息return new UserOrderInfo(user, order);
}).thenAccept(userOrderInfo -> {System.out.println("User: " + userOrderInfo.getUser().getName() + ", Order: " + userOrderInfo.getOrder().getOrderId());
});
6.2 使用thenCompose进行任务组合
thenCompose用于连接两个CompletableFuture,使得第二个任务在第一个任务完成后执行,并且第一个任务的结果作为参数传递给第二个任务。
应用场景:当第一个任务的结果决定了第二个任务的执行,例如根据用户ID获取用户详细信息。
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> {return new User("John", 30);
});CompletableFuture<UserDetails> userDetailsFuture = userFuture.thenCompose(user -> {// 根据用户信息获取详细信息return CompletableFuture.supplyAsync(() -> {return new UserDetails(user.getName(), "john@example.com");});
}).thenAccept(userDetails -> {System.out.println("User Details: " + userDetails.getEmail());
});
通过使用thenCombine和thenCompose,我们可以灵活地组合和连接多个CompletableFuture,实现复杂的异步任务流,增强代码的可读性和逻辑性。
7. 等待多个CompletableFuture
在处理多个CompletableFuture时,我们经常需要等待它们的完成。Java提供了allOf和anyOf方法来实现这一点。
7.1 使用allOf方法
allOf方法用于等待多个CompletableFuture的完成,只有当所有任务都完成时,才能继续执行后续操作。返回一个CompletableFuture。
应用场景:当需要同时启动多个独立的异步任务,并在所有任务完成后执行某个操作时。
CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {// 模拟任务1System.out.println("Task 1 completed");
});CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {// 模拟任务2System.out.println("Task 2 completed");
});CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2);
allTasks.thenRun(() -> {System.out.println("All tasks completed!");
});
7.2 使用anyOf方法
anyOf方法用于等待多个CompletableFuture中的任意一个完成,返回一个CompletableFuture,该CompletableFuture在其中任意一个任务完成后就会完成。
应用场景:当有多个可选任务,且只需等待第一个成功完成的任务时。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {// 模拟任务1return "Result from Task 1";
});CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {// 模拟任务2return "Result from Task 2";
});CompletableFuture<Object> anyTask = CompletableFuture.anyOf(future1, future2);
anyTask.thenAccept(result -> {System.out.println("First completed task result: " + result);
});
7.3 实际应用中的最佳实践
- 错误处理:在等待多个CompletableFuture时,确保对可能出现的异常进行处理,使用
handle或exceptionally。 - 资源管理:当处理大量的CompletableFuture时,注意合理管理线程池,以避免资源耗尽。
- 组合任务:根据任务之间的依赖关系,选择使用
allOf或anyOf,确保任务按需执行。 - 可读性:保持代码的清晰性和可读性,适当使用注释以帮助理解复杂的异步逻辑。
通过合理使用allOf和anyOf,可以有效地管理多个CompletableFuture,提升应用的并发性能和响应能力。
8. 性能考虑
8.1 CompletableFuture的线程池管理
CompletableFuture默认使用ForkJoinPool.commonPool()来执行异步任务。这个线程池是共享的,适用于大多数场景,但在高并发的情况下,可能会出现资源竞争或线程饥饿的问题。为了更好地控制并发和资源利用,建议创建自定义线程池。
ExecutorService customExecutor = Executors.newFixedThreadPool(10);
CompletableFuture.supplyAsync(() -> {// 任务逻辑
}, customExecutor);
8.2 如何优化性能和资源利用
-
合理选择线程池:根据应用的需求选择合适的线程池类型(如CachedThreadPool、FixedThreadPool等),并根据任务的特点调整线程池的大小。
-
减少上下文切换:尽量避免频繁地创建和销毁线程,以减少上下文切换的开销。使用线程池可以有效解决这个问题。
-
避免过度并发:在处理大量的异步任务时,过度并发可能导致资源耗尽。可以通过限制并发数量来优化性能,使用Semaphore等工具来控制并发度。
-
使用批处理:当处理大量数据时,可以考虑将任务批量处理,减少任务提交的频率,从而提高性能。
-
异步IO操作:如果任务涉及IO操作,考虑使用异步IO以提升性能。例如,使用NIO或异步HTTP客户端。
-
监控和调整:监控应用的性能指标,根据运行情况进行调整,优化线程池配置和任务执行策略,以达到最佳性能。
9. 总结
9.1 回顾CompletableFuture的优势
- 简洁性与可读性:CompletableFuture通过链式调用和Lambda表达式使得异步编程更加简洁,代码可读性大大增强。
- 灵活的异步控制:它支持任务组合和依赖管理,能够轻松处理复杂的异步逻辑,提供了多种组合和等待的方法(如
thenCombine、thenCompose、allOf和anyOf)。 - 强大的异常处理:提供了灵活的异常处理机制,通过
exceptionally和handle方法,开发者可以优雅地处理异步任务中的错误。 - 性能优化:通过自定义线程池和合理的任务管理,可以优化资源利用,提升应用的性能。
9.2 实际应用中的注意事项和建议
- 选择合适的线程池:根据任务类型和并发需求选择合适的线程池,避免使用默认的ForkJoinPool,尤其是在高负载情况下。
- 错误处理:始终为CompletableFuture添加异常处理逻辑,确保应用在遇到异常时能够稳健运行。
- 监控与调优:定期监控应用的性能和资源使用情况,及时进行调优,以应对变化的负载和需求。
- 避免阻塞操作:确保在异步任务中尽量避免阻塞操作,保持任务的非阻塞性,以提升整体性能。
- 文档与注释:对于复杂的异步逻辑,适当添加文档和注释,帮助团队成员理解代码的执行流程和设计意图。
10. 参考资料
-
书籍
- 《Java并发编程实战》(Java Concurrency in Practice) - Brian Goetz
- 《Java 8实战》(Java 8 in Action) - Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft
- 《Effective Java》 - Joshua Bloch
-
官方文档
- Java SE 8 Documentation
- CompletableFuture Guide - Oracle
-
在线资源
- Baeldung: Guide to CompletableFuture
- Java CompletableFuture Examples - GeeksforGeeks
- Java 8 CompletableFuture - Javatpoint
相关文章:
深入理解Java CompletableFuture多线程编排的最佳实践
1. 引言 1.1 多线程编排的必要性 在现代应用程序中,尤其是涉及网络请求、大数据处理或高并发场景时,多线程编排变得尤为重要。传统的顺序执行方式可能导致性能瓶颈,增加响应时间,从而影响用户体验和系统效率。通过多线程编排&am…...
人工智能与机器学习原理精解【29】
文章目录 多层感知机(MLP, Multilayer Perceptron)通用逼近定理(Universal Approximation Theorem)一、定义二、公式三、原理 MLP(多层感知机,Multilayer Perceptron)概述一、数学原理二、公式三…...
【Python】探索 Graphene:Python 中的 GraphQL 框架
人们常说挣多挣少都要开心,这话我相信,但是请问挣少了怎么开心? 随着现代 Web 应用对数据交互需求的不断增长,GraphQL 作为一种数据查询和操作语言,越来越受到开发者的青睐。Graphene 是 Python 语言中实现 GraphQL 的…...
Azure Data Box 80 TB 现已在中国区正式发布
我们非常高兴地宣布,Azure Data Box 80 TB SKU现已在 Azure 中国区正式发布。Azure Data Box 是 Azure 的离线数据传输解决方案,允许您以快速、经济且可靠的方式将 PB 级数据从 Azure 存储中导入或导出。通过硬件传输设备可加速数据的安全传输࿰…...
“表观组学分析:汇智生物的创新技术应用“
🌱 汇智生物 | 专注农业&植物基因组分析 🌱 🎓 教授【优青】团队亲自指导!提供专业实验设计、数据分析、SCI论文辅助等全方位服务。精准高效,为农植物科研保驾护航! 🔬 专业实验外包服务&am…...
【web安全】——sql注入
1.MySQL基础 1.1information_schema数据库详解 简介: 在mysql5版本以后,为了方便管理,默认定义了information_schema数据库,用来存储数据库元数据信息。schemata(数据库名)、tables(表名tableschema)、columns(列名或字段名)。…...
vue基础面试题
1.Vue指令 v-bind:动态绑定数据 v-on:绑定事件监听器 v-for:循环指令,可以循环数组或对象 v-if:根据表达式的真假值,判断是否渲染元素,会销毁并重建 v-show:显示隐藏元素࿰…...
关系型数据库和非关系型数据库的区别
1.常见的主流数据库 关系型数据库: MySql 、达梦 、PostgreSQL 、Oracle 、Sql Server 、Sqlite非关系型数据库: Redis 、MongoDB 、HBase 、 Neo4J 、 CouchDB 2.介绍 关系型数据库最典型的数据结构是表,由二维表及其之间的联系…...
学习之什么是迭代器
什么是迭代器 迭代器的作用:访问容器中的元素 首先要了解什么是Iterablelterable(可迭代的) 字符串、列表、元组、字典都是lterable,都可以放到for循环语句中遍历 lterable类型的定义中一定有一个_iter_方法iter 方法必须返回一个lterator(迭代器) 可以…...
数据结构-3.6.队列的链式实现
队列可以理解为单链表的阉割版,相比单链表而言,队列只有在添加和删除元素上和单链表有区别 一.队列的链式实现: 1.图解: 2.代码: #include<stdio.h> typedef struct LinkNode //链式队列结点 {int data;st…...
Java中去除字符串中的空格
在平时的开发中,在后端经常要获取前端传过来的字符串,有的是用户从输入框中输入的,有的是通过excel表格中获取的。 在这些字符串中,有时候会遇到字符串中有空格、换行符或者制表符,对于这种字符串来说,直接…...
AI大模型算法工程师就业宝典—— 高薪入职攻略与转行秘籍!
从ChatGPT到新近的GPT-4,GPT模型的发展表明,AI正在向着“类⼈化”⽅向迅速发展。 GPT-4具备深度阅读和识图能⼒,能够出⾊地通过专业考试并完成复杂指令,向⼈类引以为傲的“创造⼒”发起挑战。 现有的就业结构即将发⽣重⼤变化&a…...
node-rtsp-stream、jsmpeg.min.js实现rtsp视频在web端播放
1. 服务地址(私有):https://gitee.com/nnlss/video-node-server 2.node-rtsp-stream 需要安装FFMPEG; 3.给推拉流做了开关,可借助http请求,有更好方式可联系; 4.存在问题: 1&…...
C++ 9.27
作业: 将之前实现的顺序表、栈、队列都更改成模板类 Stack #include <iostream> using namespace std; template <typename T> class Stack { private: T* arr; // 存储栈元素的数组 int top; // 栈顶索引 int capacity; // 栈的…...
让具身智能更快更强!华东师大上大提出TinyVLA:高效视觉-语言-动作模型,遥遥领先
论文链接:https://arxiv.org/pdf/2409.12514 项目链接:https://tiny-vla.github.io/ 具身智能近期发展迅速,拥有了大模型"大脑"的机械臂在动作上更加高效和精确,但现有的一个难点是:模型受到算力和数据的制…...
Excel 获取某列不为空的值【INDEX函数 | SMALL函数或 LARGE函数 | ROW函数 | ISBLANK 函数】
〇、需求 Excel 获取某列不为空的值(获取某列中第一个非空值 或 获取某列中最后一个非空值)。 一、知识点讲解 INDEX函数 和 SMALL函数 两个函数搭配使用都可以实现上述需求 获取某列中第一个非空值 。 INDEX函数 和 LARGE函数 两个函数搭配使用都可以实现上述需求 获取某…...
爆火!大模型算法岗 100 道面试题全解析,赶紧收藏!
大模型应该是目前当之无愧的最有影响力的AI技术,它正在革新各个行业,包括自然语言处理、机器翻译、内容创作和客户服务等等,正在成为未来商业环境的重要组成部分。 截至目前大模型已经超过200个,在大模型纵横的时代,不…...
Python画笔案例-068 绘制漂亮米
1、绘制漂亮米 通过 python 的turtle 库绘制 漂亮米,如下图: 2、实现代码 绘制 漂亮米,以下为实现代码: """漂亮米.py注意亮度为0.5的时候最鲜艳本程序需要coloradd模块支持,安装方法:pip install coloradd程序运行需要很长时间,请耐心等待。可以把窗口最小…...
得物App荣获国家级奖项,正品保障引领潮流电商新风尚
近日,在2024年中国国际服务贸易交易会上,得物App凭借其在科技创新保障品质消费领域的突出成果,再次荣获国家级殊荣——“科技创新服务示范案例”。这是继上海市质量金奖之后,得物App获得的又一个“高含金量”奖项。 作为深受年轻人…...
【BurpSuite】SQL注入 | SQL injection(1-2)
🏘️个人主页: 点燃银河尽头的篝火(●’◡’●) 如果文章有帮到你的话记得点赞👍收藏💗支持一下哦 【BurpSuite】SQL注入 | SQL injection(1-2) 实验一 Lab: SQL injection vulnerability in WHERE clause…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
ui框架-文件列表展示
ui框架-文件列表展示 介绍 UI框架的文件列表展示组件,可以展示文件夹,支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项,适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...
