从 Future 到 CompletableFuture:简化 Java 中的异步编程

引言

在并发编程中,我们经常需要处理多线程的任务,这些任务往往具有依赖性,异步性,且需要在所有任务完成后获取结果。Java 8 引入了 CompletableFuture 类,它带来了一种新的编程模式,让我们能够以函数式编程的方式处理并发任务,显著提升了代码的可读性和简洁性。

在这篇博客中,我们将深入探讨 CompletableFuture 的设计原理,详细介绍其 API 的使用方式,并通过具体的示例来展示其在并发任务处理中的应用。我们也将探讨其与 Future,CompletableFuture 以及 Java 并发包中其他工具的对比,理解何时以及为什么需要使用 CompletableFuture。让我们一起踏上这个富有挑战性的学习之旅吧!

在开始之前,我们先来回顾一下Java语言发展历史

Java 并发编程的演进

自从诞生以来,Java 就一直致力于提供强大的并发和异步编程工具。在最初的 JDK 1.4 时期,Java 开发者需要使用低级的并发控制工具,如 synchronized 和 wait/notify,这些工具虽然功能强大,但使用起来非常复杂。

为了简化并发编程,Java 在 JDK 1.5 中引入了JUC包,提供了一系列高级的并发控制工具,如 ExecutorService、Semaphore 和 Future。

我们先来看下,Future到底是怎么进行异步编程的

Future的异步编程之旅

在开始我们的旅程之前,我们先看看一下这个需求。

一个复杂的需求

假设你正在为一家在线旅行社工作,用户可以在网站上搜索并预订飞机票和酒店。以下是你需要处理的一系列操作:

  • 根据用户的搜索条件,查询所有可用的飞机票
  • 对每一个飞机票,查询与之匹配的可用酒店
  • 对每一个飞机票和酒店的组合,计算总价格
  • 将所有的飞机票和酒店的组合按照价格排序
  • 将结果返回给用户

实现

为了实现这个需求,首先,我们需要创建一个 ExecutorService,:

ExecutorService executor = Executors.newFixedThreadPool(10);
// 1. 查询飞机票
Future<List<Flight>> futureFlights = executor.submit(() -> searchFlights(searchCondition));List<Flight> flights;
try {flights = futureFlights.get();
} catch (InterruptedException | ExecutionException e) {// 处理异常
}// 2. 对每个飞机票查询酒店
List<Future<List<Hotel>>> futureHotelsList = new ArrayList<>();
for (Flight flight : flights) {Future<List<Hotel>> futureHotels = executor.submit(() -> searchHotels(flight));futureHotelsList.add(futureHotels);
}List<Future<List<TravelPackage>>> futureTravelPackagesList = new ArrayList<>();
for (Future<List<Hotel>> futureHotels : futureHotelsList) {List<Hotel> hotels;try {hotels = futureHotels.get();} catch (InterruptedException | ExecutionException e) {// 处理异常}// 3. 对每个飞机票和酒店的组合计算总价格for (Hotel hotel : hotels) {Future<List<TravelPackage>> futureTravelPackages = executor.submit(() -> calculatePrices(flight, hotel));futureTravelPackagesList.add(futureTravelPackages);}
}List<TravelPackage> travelPackages = new ArrayList<>();
for (Future<List<TravelPackage>> futureTravelPackages : futureTravelPackagesList) {try {travelPackages.addAll(futureTravelPackages.get());} catch (InterruptedException | ExecutionException e) {// 处理异常}
}// 4. 将所有的旅行套餐按照价格排序
travelPackages.sort(Comparator.comparing(TravelPackage::getPrice));// 5. 返回结果
return travelPackages;

需求终于做完了(叹气声)。此时此刻,生在JDK8+的你,会不会感同身受呢。这还是在没有处理异常,没有很多业务代码的前提下。好,现在缓一下我们继续。我们可以从上面代码最直观的看到什么?

再完美的表达,也敌不过一个让你直观感受的例子。接下来,我们来分析一下Future的缺点。

分析这趟Future异步编程之旅

从上面的 Future 的例子中,我们可以明显看到以下几点缺点:

回调地狱

Future 的实现使得我们必须在每一个 Future 完成后启动另一个 Future,这使得代码看起来像是在不断嵌套回调。这种方式会使得代码难以阅读和理解,特别是在涉及复杂的异步任务链时。

阻塞操作

虽然 Future.get() 可以得到任务的结果,但这是一个阻塞操作,它会阻止当前线程的执行,直到异步操作完成。这种设计对于要实现非阻塞的异步编程来说,是非常不理想的。

复杂的错误处理

在使用 Future 链式处理异步任务时,如果中间某个环节出现错误,错误处理的复杂性就会大大增加。你需要在每个 Future 的处理过程中都增加异常处理代码,这使得代码变得更加复杂和难以维护。

无法表示任务间复杂关系

使用 Future 很难直观地表示出任务之间的依赖关系。例如,你无法使用 Future 来表示某个任务需要在另外两个任务都完成后才能开始,或者表示多个任务可以并行执行但是必须在一个共同的任务之前完成。这种限制使得 Future 在处理复杂的异步任务链时变得非常困难。

因此,为了解决这些问题,CompletableFuture 被引入了 Java 8,提供了更强大和灵活的异步编程工具。

CompletableFuture的异步编程之旅

同样还是上面的例子,我们来看下它的实现代码:

CompletableFuture.supplyAsync(() -> searchFlights())  // 1. 查询飞机票.thenCompose(flights -> {  // 2. 对每个飞机票查询酒店List<CompletableFuture<List<TravelPackage>>> travelPackageFutures = flights.stream().map(flight -> CompletableFuture.supplyAsync(() -> searchHotels(flight))  // 查询酒店.thenCompose(hotels -> {  // 3. 对每个飞机票和酒店的组合计算总价格List<CompletableFuture<TravelPackage>> packageFutures = hotels.stream().map(hotel -> CompletableFuture.supplyAsync(() -> new TravelPackage(flight, hotel))).collect(Collectors.toList());return CompletableFuture.allOf(packageFutures.toArray(new CompletableFuture[0])).thenApply(v -> packageFutures.stream().map(CompletableFuture::join).collect(Collectors.toList()));})).collect(Collectors.toList());return CompletableFuture.allOf(travelPackageFutures.toArray(new CompletableFuture[0])).thenApply(v -> travelPackageFutures.stream().flatMap(future -> future.join().stream()).collect(Collectors.toList()));}).thenApply(travelPackages -> {  // 4. 将所有的旅行套餐按照价格排序return travelPackages.stream().sorted(Comparator.comparing(TravelPackage::getPrice)).collect(Collectors.toList());}).exceptionally(e -> {  // 处理所有的异常// 处理异常return null;});

你可能乍一看,感觉怎么比Future还要复杂。但是实际在业务中,它反而更加容易读懂。每一步,每一个操作都可以顺着thenCompose下去。

分析这趟CompletableFuture异步编程之旅

CompletableFuture 是 Java 8 中引入的,用于解决在使用 Future 时遇到的一些问题。它实现了 Future 和 CompletionStage 接口,并且提供了大量的方法来帮助你更好地控制和管理异步操作。我们来结合上面的例子来分析它的优点:

链式编程

我们使用 CompletableFuture 中的 supplyAsync 方法来异步地开始查询航班的操作:

CompletableFuture<List<Flight>> flightsFuture = CompletableFuture.supplyAsync(() -> searchFlights(source, destination));

然后,我们使用 thenCompose 方法将查询航班和查询酒店的操作连在一起:

CompletableFuture<List<TravelPackage>> travelPackagesFuture = flightsFuture.thenCompose(flights ->CompletableFuture.supplyAsync(() -> flights.stream().map(flight -> searchHotels(flight)).collect(Collectors.toList())));

非阻塞操作

上述的 thenCompose 方法是非阻塞的,即查询酒店的操作会立即开始,而不需要等待查询航班的操作完成。

异常处理

我们使用 exceptionally 方法处理查询航班和查询酒店过程中可能出现的异常:

CompletableFuture<List<TravelPackage>> travelPackagesFuture = flightsFuture.thenCompose(flights ->CompletableFuture.supplyAsync(() -> flights.stream().map(flight -> searchHotels(flight)).collect(Collectors.toList()))).exceptionally(ex -> {System.out.println("失败了: " + ex);return new ArrayList<>();});

表示任务间复杂关系

我们使用 CompletableFuture.allOf 方法来表示所有的旅行套餐计算任务都必须在开始排序之前完成:

CompletableFuture<List<TravelPackage>> sortedTravelPackagesFuture = travelPackagesFuture.thenApply(travelPackages ->travelPackages.stream().flatMap(List::stream).sorted(Comparator.comparing(TravelPackage::getPrice)).collect(Collectors.toList()));

暂停一分钟,再细细体会上面的例子。我们接着来集中比较这两者

CompletableFuture与Future的比较

异步执行与结果获取

  • Future 提供了一种在未来某个时间点获取结果的方式,但它的主要问题是在获取结果时,如果结果尚未准备好,会导致阻塞。另外,使用 isDone() 方法进行轮询也不是一个好的选择,因为它将消耗CPU资源。
  • CompletableFuture 提供了非阻塞的结果获取方法,thenApply, thenAccept, thenRun 等方法可以在结果准备好后被自动执行,这样我们不需要手动检查和等待结果。

链式操作

  • Future 不支持链式操作,我们无法在 Future 完成后自动触发另一个任务。
  • CompletableFuture 提供了 thenApply, thenAccept, thenRun, thenCompose, thenCombine 等一系列方法,用于在当前任务完成后自动执行另一个任务,形成任务链。

异常处理

  • 在 Future 中,只能通过 get() 方法获取异常,但是这种方式会阻塞线程,直到任务执行完毕。
  • CompletableFuture 提供了 exceptionally, handle 等方法,我们可以用这些方法在发生异常时提供备用的结果,或者对异常进行处理。

任务组合

  • Future 并未提供任何任务组合的方式。
  • CompletableFuture 提供了 allOf, anyOf, thenCombine 等方法,我们可以通过这些方法来表示任务间的并行关系,或者汇聚关系。

灵活的任务执行控制

  • Future 在任务执行上相对较为死板,我们无法中途取消任务,也无法在任务结束后执行特定操作。
  • CompletableFuture 提供了 cancel, complete 等方法,用于中途取消任务,或者提前完成任务。此外,whenComplete 和 whenCompleteAsync 方法允许我们在任务结束时,无论成功或失败,都可以执行特定的操作。

假如有一个面试官现在问题它们两者的区别,你会回答了吗?接下来,我们来解析一下

进阶 | 理解CompletableFuture原理

为了让你理解的不那么晦涩,我为你讲生活中的例子:

我们可以把 CompletableFuture 想象成一家装配线生产车间。每一件零件(任务)的加工完成(Future 完成)都可能会触发下一步工作(下一步的操作),而每一步工作的完成都会通知车间(Future),以便开始下一个阶段的生产。这个过程就像一条流水线,每完成一个步骤就自动进行下一个。

带着这个场景,我们接着往下看。

任务链

CompletableFuture 的源码中,有一个内部类 Completion,代表了任务链中的一项任务。每当一个任务完成时,它都会尝试去完成依赖于它的任务,就像流水线上的工人完成了一部分工作后,就会把半成品传递给下一个工人。

abstract static class Completion extends ForkJoinTask<Void> implements Runnable, AsynchronousCompletionTask {// ...
}

结果容器

CompletableFuture 本身就是一个结果容器,它持有了执行的结果,包括正常的计算结果或者执行过程中出现的异常。

volatile Object result; // The outcome of the computation

工作线程

所有的异步任务都会提交到 ForkJoinPool.commonPool() 中进行执行,当然也可以指定自定义的 Executor 来执行任务。

static final ForkJoinPool ASYNC_POOL = ForkJoinPool.commonPool();

任务触发

当一个任务完成后,CompletableFuture 会通过 tryFire 方法触发与之关联的下一个任务。这就好比工人完成了一部分工作后,通知流水线的下一位工人继续完成接下来的工作。

 final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {// ...if (a != null && a.stack != null) {if (mode < 0)a.cleanStack();elsea.postComplete();}if (b != null && b.stack != null) {if (mode < 0)b.cleanStack();elseb.postComplete();}return null;}

是不是有点理解了呢?我可以肯定的说,你已经超过80%的人了!

CompletableFuture的主要方法

细心的你肯定发现了,CompletableFuture大多数方法都实现于一个CompletionStage接口。当然,我在这里可以为你把所有方法都试过一遍,但是你肯定会看的特别累。这样!我把上面需求中所用到的方法都为你讲解,剩下的请你结合网上的案例学习。

supplyAsync()方法

这个方法用于异步执行一个供应函数,并返回一个CompletableFuture对象。在我们的示例中,这个方法用于启动一个异步任务来查找航班。

CompletableFuture<List<Flight>> flightsFuture = CompletableFuture.supplyAsync(() -> searchFlights(destination));

thenCompose()方法

这个方法用于链接多个CompletableFuture对象,形成一个操作链。当一个操作完成后,thenCompose()方法会将操作的结果传递给下一个操作。在我们的示例中,这个方法用于在找到航班之后查找酒店。

CompletableFuture<List<Hotel>> hotelsFuture = flightsFuture.thenCompose(flights -> CompletableFuture.supplyAsync(() -> searchHotels(destination)));

thenCombine()方法

这个方法用于将两个独立的CompletableFuture对象的结果合并为一个结果。在我们的示例中,这个方法用于将查找航班和酒店的结果合并为一个旅行套餐。

CompletableFuture<List<TravelPackage>> travelPackagesFuture = flightsFuture.thenCombine(hotelsFuture, (flights, hotels) -> createTravelPackages(flights, hotels));

thenAccept()方法

这个方法在CompletableFuture对象完成计算后执行一个消费函数,接收计算结果作为参数,不返回新的计算值。在我们的示例中,这个方法用于打印出所有的旅行套餐。

travelPackagesFuture.thenAccept(travelPackages -> printTravelPackages(travelPackages));

allOf()方法

这个方法用于将一个CompletableFuture对象的数组组合成一个新的CompletableFuture对象,这个新的CompletableFuture对象在数组中所有的CompletableFuture对象都完成时完成。在我们的示例中,这个方法用于将每个航班与每个酒店的组合结果(也就是旅行套餐)组合在一起。

CompletableFuture.allOf(packageFutures.toArray(new CompletableFuture[0])).thenApply(v -> packageFutures.stream().map(CompletableFuture::join).collect(Collectors.toList()));

thenApply()方法

这个方法用于对CompletableFuture的结果进行变换,并返回一个新的CompletableFuture对象。在我们的示例中,这个方法用于将查询到的旅行套餐按照价格进行排序。

.thenApply(travelPackages -> {  // 4. 将所有的旅行套餐按照价格排序return travelPackages.stream().sorted(Comparator.comparing(TravelPackage::getPrice)).collect(Collectors.toList());})

exceptionally()方法

这个方法用于处理CompletableFuture的异常情况。如果CompletableFuture的计算过程中抛出异常,那么这个方法会被调用。在我们的示例中,这个方法用于处理查询旅行套餐过程中可能出现的任何异常。

.exceptionally(e -> {  // 处理所有的异常// 处理异常return null;});

当然,这些方法已经够你用了。除非这个需求比我想得还复杂,那算你厉害。哦,不对,算需求变态。现在,你可以挥起历史的毛笔续写了吗?

Java 并发编程的续章

JDK 1.5 的 Future 解决了许多并发编程的复杂性,但是它仍有一些局限性。Future 只能描述一个异步操作,并不能描述一个由多个步骤组成的异步操作。例如,当需要处理一个由多个异步操作序列组成的业务流程时,你可能会发现你的代码被复杂的回调逻辑淹没,这就是人们常说的回调地狱。此外,Future 没有提供一种有效的方式来处理异步操作的结果,你只能通过阻塞调用 get() 方法来获取结果。

为了解决这些问题,Java 在 JDK 1.8 中引入了 CompletableFuture。CompletableFuture 是 Future 的增强版,它不仅能表示一个异步操作,还可以通过 thenCompose(), thenCombine(), allOf() 等方法来描述一个由多个步骤组成的异步操作。通过这些方法,CompletableFuture 能以流畅的链式调用的方式来描述复杂的异步业务流程,这大大简化了异步编程的复杂性。

常见面试题

  • 请解释一下 Future 接口在 Java 中的用途?
  • 解释一下 Future 的局限性是什么?
  • 请解释一下 CompletableFuture 的用途以及它如何克服 Future 的局限性?
  • 如何用 CompletableFuture 来表示一组并行的异步操作?
  • 请解释一下 CompletableFuture 的 thenApply(),thenCompose(),和 thenCombine() 方法的作用及区别?
  • 如果你有一个耗时的异步操作需要执行,但是你又不希望调用 get() 方法时阻塞,你可以使用 CompletableFuture 的哪个方法来达到这个目的?
  • 如何处理 CompletableFuture 的异常?
  • 请解释一下 CompletableFuture 的工作原理?

阅读完文章的你,是否可以回答这些问题呢?我在留言等你。

总结

好了,到这里就结束了,我们来回顾一下。首先,我带你回顾了一下Java并发世界的编年史。紧接着,我带你体验了一下古人经常使用的Future。感到它的不妙之后,我带你回到CompletableFuture 。紧接着有深入了解了它的全貌以及使用方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/55502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring MVC详解

文章目录 一、SpringMVC1.1 引言1.2 MVC架构1.2.1 概念1.2.2 好处 二、开发流程2.1 导入依赖2.2 配置核心(前端)控制器2.3 后端控制器2.4 配置文件2.5 访问 三、接收请求参数3.1 基本类型参数3.2 实体收参【重点】3.3 数组收参3.4 集合收参 【了解】3.5 路径参数3.6 中文乱码 四…

实时同步ES技术选型:Mysql+Canal+Adapter+ES+Kibana

基于之前的文章&#xff0c;精简操作而来 让ELK在同一个docker网络下通过名字直接访问Ubuntu服务器ELK部署与实践使用 Docker 部署 canal 服务实现MySQL和ES实时同步Docker部署ES服务&#xff0c;canal全量同步的时候内存爆炸&#xff0c;ES/Canal Adapter自动关闭&#xff0c…

什么是回调函数(callback function)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 回调函数&#xff08;Callback Function&#xff09;⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这…

C#---第二十: partial修饰类的特性及应用

0.知识背景 局部类型适用于以下情况&#xff1a; 类型特别大&#xff0c;不宜放在一个文件中实现。一个类型中的一部分代码为自动化工具生成的代码&#xff0c;不宜与我们自己编写的代码混合在一起。需要多人合作编写一个类 局部类型的限制: 局部类型只适用于类、接口、结构&am…

手写数字识别之优化算法:观察Loss下降的情况判断合理的学习率

目录 手写数字识别之优化算法:观察Loss下降的情况判断合理的学习率 前提条件 设置学习率 学习率的主流优化算法 手写数字识别之优化算法:观察Loss下降的情况判断合理的学习率 我们明确了分类任务的损失函数&#xff08;优化目标&#xff09;的相关概念和实现方法&#xff…

Flutter Cannot run with sound null safety, because the following dependencies

flutter sdk 版本升级到2.0或者更高的版本后&#xff0c;运行之前的代码会报错 Error: Cannot run with sound null safety, because the following dependencies dont support null safety:- package:flutter_swiper- package:flutter_page_indicator- package:transformer_p…

微服务中间件--统一网关Gateway

统一网关Gateway 8.统一网关Gatewaya.搭建网关服务b.路由断言工厂c.路由过滤器GatewayFilterd.全局过滤器GlobalFiltere.过滤器的执行顺序f.网关的cors跨域配置 8.统一网关Gateway 网关功能&#xff1a; 身份认证和权限校验服务路由、负载均衡请求限流 网关的技术实现 在Spr…

VUE笔记(三)vue的语法

一、计算属性 1、计算属性的概念 计算属性是依赖于源数据(data或者属性中的数据)&#xff0c;在元数据的基础上进行逻辑运算后得到的新的数据&#xff0c;计算属性要依赖于源数据&#xff0c;源数据数据变化计算属性也会变化 2、计算属性的语法 在vue2中使用computed这个选…

threejs纹理加载三(视频加载)

threejs中除了能把图片作为纹理进行几何体贴图以外&#xff0c;还可以把视频作为纹理进行贴图设置。纹理的类型有很多&#xff0c;我们可以用不同的加载器来加载&#xff0c;而对于视频作为纹理&#xff0c;我们需要用到今天的主角&#xff1a;VideoTexture。我们先看效果&…

Nacos安装指南

Nacos安装指南 1.Windows安装 开发阶段采用单机安装即可。 1.1.下载安装包 在Nacos的GitHub页面&#xff0c;提供有下载链接&#xff0c;可以下载编译好的Nacos服务端或者源代码&#xff1a; GitHub主页&#xff1a;https://github.com/alibaba/nacos GitHub的Release下载…

FxFactory 8 Pro Mac 苹果电脑版 fcpx/ae/motion视觉特效软件包

FxFactory pro for mac是应用在Mac上的fcpx/ae/pr视觉特效插件包&#xff0c;包含了成百上千的视觉效果&#xff0c;打包了很多插件&#xff0c;如调色插件&#xff0c;转场插件&#xff0c;视觉插件&#xff0c;特效插件&#xff0c;文字插件&#xff0c;音频插件&#xff0c;…

java八股文面试[多线程]——什么是守护线程

知识来源&#xff1a; 【2023年面试】什么是守护线程_哔哩哔哩_bilibili

Android JNI系列详解之ndk编译工具环境变量配置

一、前提 之前是只介绍了CMake编译工具的使用&#xff0c;现在介绍另一种原生&#xff08;NDK自带的脚本工具&#xff09;自带的编译方式&#xff1a;ndk-build&#xff0c;想要使用ndk-build编译工程&#xff0c;我们需要配置全局的环境变量。 二、配置环境变量 找到ndk在电脑…

系统架构主题之七:基于架构的软件设计方法及应用

1 基于架构的软件设计方法概念 关键词&#xff1a;ABSD、自顶向下、递归迭代、与需求同步、设计元素、视角与视图、用例和质量场景、预期和非预期等。 总的来讲&#xff0c;ABSD方法分为如下六个大的阶段&#xff1a; 1&#xff09;体系结构需求阶段 相比传统软件系统设计&…

设计模式 06 适配器模式

适配器模式&#xff08;Adapter Pattern&#xff09;属于结构型模式 概述 结构型模式关注如何将现有的类或对象组织在一起形成更加强大的结构。 在生活中&#xff0c;我们经常遇到这样的一个问题&#xff1a;轻薄笔记本通常只有 type-c 或者 usb-a 接口&#xff0c;没有网口。…

《Zookeeper》源码分析(二十三)之 客户端的命令处理过程

目录 客户端的命令处理过程1. ZooKeeper.create()2. ClientCnxn.submitRequest()3. SendThread.run()4. ClientCnxnSocket.doTransport()5. SendThread.readResponse() 客户端的命令处理过程 以创建节点命令为例&#xff0c;整个过程流程如下&#xff1a; CliCommand命令在抽…

AcWing 2058. 笨拙的手指(每日一题)

大家好 我是寸铁 如果你觉得这篇题解对你有用&#xff0c;可以动动手点个赞或关注&#xff0c;谢谢~ 题目描述 输入的第一串字母&#xff0c;存在一位错误。 输入的第二串字母&#xff0c;存在一位错误。 答案保证唯一解 我们需要去枚举每一位&#xff0c;找到二进制和三进制…

Linux安装ntp并使用阿里云配置ntp服务器

安装 NTP 客户端&#xff1a; 打开终端&#xff0c;以 root 权限执行以下命令来安装 NTP 客户端&#xff1a; sudo zypper install ntp 编辑 NTP 配置文件&#xff1a; 使用文本编辑器打开 NTP 的配置文件 /etc/ntp.conf&#xff0c;例如使用 nano 编辑器&#xff1a; sudo v…

数据库——MySQL高性能优化规范

文章目录 数据库命令规范数据库基本设计规范1. 所有表必须使用 Innodb 存储引擎2. 数据库和表的字符集统一使用 UTF83. 所有表和字段都需要添加注释4. 尽量控制单表数据量的大小,建议控制在 500 万以内。5. 谨慎使用 MySQL 分区表6.尽量做到冷热数据分离,减小表的宽度7. 禁止在…

Vue 和 JQuery 的区别在哪?为什么 JQuery 会被 Vue 取代?

在 Web 前端开发领域&#xff0c;我们经常会遇到一些不同的工具和框架&#xff0c;其中 Vue 和 JQuery, JQuery 是曾经备受欢迎的选择&#xff0c;而现在 Vue 是大多数人的选择。本文将探讨 Vue 和 JQuery 之间的区别&#xff0c;并讨论为什么越来越多的开发人员放弃 JQuery 而…