Java高阶私房菜:快速学会异步编程CompletableFuture

news2025/1/11 23:49:30

        为了使主程代码不受阻塞之苦,一般使用异步编程,而异步编程架构在JDK1.5便已有了雏形,主要通过Future和Callable实现,但其操作方法十分繁琐,想要异步获取结果,通常要以轮询的方式去获取结果,具体如下:

  public static void testFuture1() throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //定义一个异步任务
        Future<String> future = executorService.submit(()->{
            Thread.sleep(3000);
            return "Future异步请求方法";
        });

        //轮询获取结果,耗费的CPU资源
        while (true){
            if(future.isDone()) {
                System.out.println(future.get());
                break;
            }
        }
    }

        在JDK8后首次引入的CompletableFuture,简化异步编程复杂性,提供了函数式编程让代码更加简洁,可以在任务完成后做对应的callback回调处理。接下来,我带你一步步了解并掌握CompletableFuture。

什么是CompletableFuture

        在项目开发中,由于业务规划逻辑的原因,业务需要从多个不同的地方获取数据,然后汇总处理为最终的结果,再返回给请求的调用方,就是聚合信息处理类的处理逻辑。如果常用串行请求,则接口响应时间长;那么利用CompletableFuture则可以大大提升性能。针对多任务,需要进行任务编排调度,也可以使用CompletableFuture进行完成。
        其内部就是实现了Future和CompletionStage接口,相当于一个Task编排工具。
        Future表示了其异步计算的结果,它提供了检查计算是否完成的方法,以等待计算的完成。计算完成后只能使用 get 方法来获取结果,有cancel、get、isDone、isCancelled等方法。                

        CompletionStage是Java8新增接口,用于异步执行中的阶段处理,CompletableFuture就是其中的一个实现类。负责对任务处理可以构造一条结果传递链,在结果传递过程中任何一个CompletionStage都可以对结果进行处理,包括异常处理、类型转换,可以构造非常简单的传递链也可以构造很复杂的传递链,几个CompletionStage可以串联起来,一个完成的阶段可以触发下一阶段的执行。
        当前的Task到底由那个Thread执行,使用的不好可能会有性能问题, 根据CompletableFuture的方法命名可以掌握。

        xxxx():表示该方法将继续在当前执行CompletableFuture的方法线程中执行;
        xxxxAsync():表示异步,在线程池中执行。在没有指定线程池的情况下,使用的是CompletableFuture内部的线程池 ForkJoinPool ,线程数默认是 CPU 的核心数。一般不要所有业务共用一个线程池,避免有任务执行一些很慢的 I/O 操作,会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,影响整个系统的性能。

方法API

        CompletableFuture静态方法,执行异步任务的API

//无返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
public static CompletableFuture<Void>   runAsync(Runnable runnable)

//无返回值,可以自定义线程池
public static CompletableFuture<Void>  runAsync(Runnable runnable, Executor executor)


//有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier)

//有返回值,可以自定义线程池
public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier, Executor executor)

        CompletableFuture对象,获取结果的API

//如果返回值没有返回,一直阻塞
V get()

//设置等待超时的时间
V get(long timeout,Timeout unit);

//有返回值就返回, 线程抛出异常就返回设置的默认值
T getNow(T defaultValue);

        CompletableFuture对象,其他重点API

//方法无返回值,当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数
thenAccept

//方法有返回值,当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数
thenApply

//对不关心上一步的计算结果,执行下一个操作
thenRun

异步编程具体代码


public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {

        testFuture3();

        System.out.println("主线程操作其他----");
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("主线程执行完成");
    }


    //简单案例
    public static void testFuture2() throws ExecutionException, InterruptedException {

        //有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) { }
            return "supplyAsync";
        });
        System.out.println("future1返回值:" + future1.get()); //输出 supplyAsync
    }



    //任务编排案例,有返回值
    public static void testFuture3() throws ExecutionException, InterruptedException, TimeoutException {

        //有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) { }
            System.out.println("执行任务一");
            return "supplyAsync";
        });
        System.out.println("future1返回值:" + future1.get()); //输出 supplyAsync

        //有返回值,当前任务正常完成以后执行,当前任务的执行的结果会作为下一任务的输入参数
        CompletableFuture<String> future2 = future1.thenApply((element) -> {
            System.out.println("入参:"+element);
            System.out.println("执行任务二");
            try {
                TimeUnit.SECONDS.sleep(6);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "thenApply";
        });

        System.out.println("future2返回值:" + future2.get(1, TimeUnit.SECONDS));

    }



    //任务编排案例,无返回值
    public static void testFuture4() throws ExecutionException, InterruptedException, TimeoutException {

        //有返回值,默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() ->{
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) { }
            System.out.println("执行任务一");
            return "supplyAsync";
        });

        //无返回值,当前任务正常完成以后执行,当前任务的执行结果可以作为下一任务的输入参数
        CompletableFuture<Void> future2 = future1.thenAccept((element) -> {
            System.out.println("入参:"+element);
            System.out.println("执行任务二");
        });

        //System.out.println("future2返回值:" + future2.get(1, TimeUnit.SECONDS));
        System.out.println("future2返回值:" + future2.get());

    }

}

CompletableFuture嵌套案例

        日常的任务中,通常定义的方法都会返回 CompletableFuture 类型,方便后续操作,然后将该任务的执行结果Future作为方法入参然后执行指定的方法, 返回一个新的CompletableFuture任务它们之间存在着业务逻辑上的先后顺序。thenCompose用来连接两个CompletableFuture,是生成一个新的CompletableFuture,用于组合多个CompletableFuture,也可以使用 thenApply() 方法来描述关系,但返回的结果就会发生 CompletableFuture 的嵌套,CompletableFuture<CompletableFuture< Product >>  这样的情况,需要get两次

具体代码

public class Product {

    private int id;

    private String title;

    private String detail;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", detail='" + detail + '\'' +
                '}';
    }
}

public class ProductDetailService {

    private static final Map<Integer,String> map = new HashMap<>();
    static {
        map.put(1,"java-详情");
        map.put(2,"python-详情");
        map.put(3,"c#-详情");
        map.put(4,"spring-详情");
        map.put(5,"springboot-详情");
        map.put(6,"harbor-详情");
        map.put(7,"mybatis-详情");
    }

    public String getById(int id){
        try {
            Thread.sleep(1000);
            System.out.println("DetailService # getById方法运行线程:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return map.get(id);
    }

}


public class ProductService {

    private static final Map<Integer,String> map = new HashMap<>();
    static {
        map.put(1,"java");
        map.put(2,"python");
        map.put(3,"c#");
        map.put(4,"spring");
        map.put(5,"springboot");
        map.put(6,"harbor");
        map.put(7,"mybatis");
    }

    public String getById(int id){
        try {
            Thread.sleep(1000);
            System.out.println("ProductService # getById方法运行线程:"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return map.get(id);
    }

}

public class Test1 {
    public static void main(String[] args) throws Exception {
        testComposeFuture();
    }
    //之前的方案, 方法的返回值也是CompletableFuture,则会出现嵌套
    public static void testEmbedFuture() throws Exception {

        ProductService productService = new ProductService();
        ProductDetailService detailService = new ProductDetailService();

        int id = 1;
        CompletableFuture<CompletableFuture<Product>> future = CompletableFuture.supplyAsync(() -> {
            String title = productService.getById(id);
            Product product = new Product();
            product.setTitle(title);
            product.setId(id);
            System.out.println("步骤1:主线程:" + Thread.currentThread().getName()+",获取产品信息:"+ product);
            return product;
        }).thenApply(new Function<Product, CompletableFuture<Product>>() {
            @Override
            public CompletableFuture<Product> apply(Product product) {
                return CompletableFuture.supplyAsync(() -> {
                    //用到了上个线程的返回值
                    String detail = detailService.getById(product.getId());
                    product.setDetail(detail);
                    System.out.println("步骤2:主线程:" + Thread.currentThread().getName()+",获取产品信息:"+ product);
                    return product;
                });
            }
        });
        System.out.println("线程:" + Thread.currentThread().getName() + " 结果:" + future.get().get().toString());
    }


    //现在的方案
    public static void testComposeFuture() throws Exception {

        ProductService productService = new ProductService();
        ProductDetailService detailService = new ProductDetailService();

        int id = 1;
        CompletableFuture<Product> future = CompletableFuture.supplyAsync(() -> {
                    String title = productService.getById(id);
                    Product product = new Product();
                    product.setTitle(title);
                    product.setId(id);
                    System.out.println("步骤1:主线程:" + Thread.currentThread().getName()+",获取产品信息:"+ product);
                    return product;
                })
                .thenCompose(product -> CompletableFuture.supplyAsync(() -> {
                    String detail = detailService.getById(product.getId());
                    product.setDetail(detail);
                    System.out.println("步骤2:主线程:" + Thread.currentThread().getName()+",获取产品信息:"+ product);
                    return product;

                }));

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + future.get().toString());
    }

}

thenCombine合并CompletableFuture案例

        需要请求两个个接口,然后把对应的CompletableFuture进行合并,返回一个新的CompletableFuture

具体代码

public static void testFuture6() throws Exception {

        ProductService productService = new ProductService();
        ProductDetailService detailService = new ProductDetailService();

        int id = 1;
        //第1个任务
        CompletableFuture<Product> baseProductFuture = CompletableFuture.supplyAsync(() -> {
            String title = productService.getById(id);
            Product product = new Product();
            product.setTitle(title);
            product.setId(id);
            return product;
        });

        //第2个任务
        CompletableFuture<Product> detailProductFuture = CompletableFuture.supplyAsync(() -> {
            String detail = detailService.getById(id);
            Product product = new Product();
            product.setDetail(detail);
            product.setId(id);
            return product;
        });


        //将上面2个任务的返回结果baseProduct和detailProduct合并,返回新的包括全部的
        CompletableFuture<Product> resultFuture = baseProductFuture
                .thenCombine(detailProductFuture,
                        new BiFunction<Product, Product, Product>() {
                            @Override
                            public Product apply(Product base, Product detail) {
                                base.setDetail(detail.getDetail());
                                return base;
                            }
                        }
                );

        System.out.println("线程:" + Thread.currentThread().getName() +
                " 结果:" + resultFuture.get().toString());

    }

多个CompletableFuture任务组合调度

        前面学习处理两个 Future 的关系,如果超过两个Future,如何处理他们的一些聚合关系呢?
 方法 allOf  和 anyOf两个函数都是静态函数,参数是变长的 CompletableFuture 的集合,前者是「与」,后者是「或」。
        allOf 返回值是 CompletableFuture< Void >类型,因为allOf没有返回值,所以通过thenApply,获取每个 CompletableFuture 的执行结果。
        anyOf 只要有任意一个 CompletableFuture 结束,就可以做接下来的事情,不像 allOf 要等待所有的 CompletableFuture 结束,每个 CompletableFuture 的返回值类型都可能不同,无法判断是什么类型, 所以 anyOf 的返回值是 CompletableFuture< Object >类型。

具体代码

public class Test2 {
    public static void testAllOf() throws Exception {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成");
            return "future1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future2完成");
            return "future2";
        });
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future3完成");
            return "future3";
        });
        CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2, future3);
        //阻塞,直到所有任务结束。
        System.out.println(Thread.currentThread().getName() + ":" + LocalDateTime.now() + ":阻塞");
        //调用join方法等待全部任务完成
        all.join();
        if (all.isDone()) {
            //一个需要耗时2秒,一个需要耗时3秒,只有当最长的耗时3秒的完成后,才会结束。
            System.out.println("全部任务完成");
        }
        System.out.println(Thread.currentThread().getName() + ":" + LocalDateTime.now() + ":阻塞结束");
    }

    public static void testAnyOf() throws Exception {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成");
            return "future1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future2完成");
            return "future2";
        });
        CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future3完成");
            return "future3";
        });
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2, future3);
        //阻塞,直到所有任务结束。
        System.out.println(Thread.currentThread().getName() + ":" + LocalDateTime.now() + ":阻塞");
        //调用join方法等待任务完成
        anyOf.join();
        if (anyOf.isDone()) {
            //一个需要耗时2秒,一个需要耗时3秒,当最短的完成则会结束
            System.out.println("全部任务完成:" + anyOf.get());
        }
        System.out.println(Thread.currentThread().getName() + ":" + LocalDateTime.now() + ":阻塞结束");
    }

}

异步编程CompletableFuture案例实战  

        微服务架构下,接口单一职责,一个页面打开涉及多个模块需要同时调用。由于需要同时建立多个连接,中间会有性能损耗,部分页面需要使用聚合接口,则可以用CompletableFuture聚合多个响应结果一次性返回。该方式可以减少建立连接数量,对于网关和服务端可以处理更多连接。
其缺点也非常明显,如果接口性能差异大,则容易性能好的接口被性能差的拖垮。其次就是需要开发更多接口,数据量大则需要更大的带宽。

具体代码

public class EduService {

    public String getRank()  {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "rank info";
    }


    public String getCategory()  {

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Category info";
    }


    public String getBanner()  {

        try {
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Banner info";
    }

    public String getVideoCard()  {

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "Video Card info";
    }


    public static void main(String[] args) throws Exception {
        System.out.println("开始:"+ LocalDateTime.now());
        Map<String, String> stringStringMap = homePageAggApi();
        System.out.println("结束:"+LocalDateTime.now());

        System.out.println(stringStringMap.toString());

        System.out.println("主线程执行完成");
    }

    private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            16,
            32,
            30,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(100000),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );

    public static Map<String,String> homePageAggApi() throws Exception {


        Map<String,String> homePageInfo = new HashMap<>();
        //模拟不同的服务调用
        EduService eduService = new EduService();

        CompletableFuture<Void> bannerFuture = CompletableFuture.runAsync(() -> {
            String banner = eduService.getBanner();
            homePageInfo.put("banner",banner);
        }, executor);

        CompletableFuture<Void> categoryFuture = CompletableFuture.runAsync(() -> {
            String category = eduService.getCategory();
            homePageInfo.put("category",category);
        }, executor);


        CompletableFuture<Void> rankFuture = CompletableFuture.runAsync(() -> {
            String rank = eduService.getRank();
            homePageInfo.put("rank",rank);
        }, executor);


        CompletableFuture<Void> videoCardFuture = CompletableFuture.runAsync(() -> {
            String videoCard = eduService.getVideoCard();
            homePageInfo.put("videoCard",videoCard);
        }, executor);


        //join()和get()方法都是阻塞调用它们的线程(通常为主线程)用来获取CompletableFuture异步之后的返回值
        CompletableFuture.allOf(bannerFuture,categoryFuture,rankFuture,videoCardFuture)
                .get();

        return homePageInfo;
    }
}

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

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

相关文章

登录github突然出现Two-factor authentication怎么办?

起因 今天想登录github下载项目&#xff0c;输入用户名密码认证之后竟然弹出来了Two-factor authentication&#xff0c;像下图&#xff0c;我只记得之前好像开启过这玩意&#xff0c;但是不知道哪里找这个code了。 解决 在你的iphone里面搜索密码。然后找到github网页&…

程序员能做什么副业?增收致富就靠它们了

作为一个程序员&#xff0c;大家都习惯了朝九晚六&#xff0c;时不时加班的生活。虽然工资听起来比其他行业高一些&#xff0c;但是&#xff0c;都是靠命拼出来的。如何摆脱这种枯燥乏味且前途未卜的生活&#xff0c;应该是很多程序员都在考虑的事情。 而作为一个做了十几年的…

【微服务-Ribbon】什么是负载均衡?微服务中负载均衡有哪些策略呢?

前面几篇文章&#xff0c;我们了解了一下Nacos的单机部署、集群部署以及微服务接入Nacos的步骤。从本篇开始&#xff0c;我们来看一下微服务第二个通用组件-负载均衡&#xff08;Ribbon&#xff09;。 1、Ribbon负载均衡器 负载均衡顾名思义&#xff0c;是指通过软件或者硬件…

javaWeb智能医疗管理系统

简介 在当今快节奏的生活中&#xff0c;智能医疗系统的崛起为医疗行业带来了一场革命性的变革。基于JavaWeb技术开发的智能医疗管理系统&#xff0c;不仅为医疗机构提供了高效、精准的管理工具&#xff0c;也为患者提供了更便捷、更个性化的医疗服务。本文将介绍一个基于SSM&a…

LInux shell编程之基础语法

目录 1、shell概述 1.1、查看Linux 提供的 Shell 解析器 1.2、bash 和 sh 的关系 1.3、查看系统&#xff08;centos&#xff09;默认使用的解释器 2、脚本的基础使用 2.1、脚本的格式 2.2、脚本的执行方式 方式一&#xff1a;采用 bash / sh 脚本的相对路径或绝对路径…

【LeetCode刷题记录】160. 相交链表

160 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函…

沂水六景记 ——齐源堂主王志强撰文

沂水六景记 ——齐源堂主王志强 明清时期&#xff0c;非常流行“八景”。所谓“八景”&#xff0c;就是在本地众多的风景名胜之中&#xff0c;选定八个最有代表性的美景&#xff0c;分别取上悦耳动听的名字&#xff0c;然后分别题诗。这种给风景选美的做法&#xff0c;是文人…

ASP.NET基于TCP协议的简单即时通信软件的设计与实现

摘 要 即时通信(Instant Message)&#xff0c;由于其具有实时性、跨平台性、成本低、效率高等优点而受到广泛的使用。设计并实现一个能够处理多用户进行实时、安全的即时通信系统具有较强的现实意义。即时通信的底层通信是通过SOCKET套接字接口实现的。当前的主流UNIX系统和微…

2W,3KVDC隔离 定电压输入,稳压单、双路输出DC-DC模块电源——TPI-2W 系列

TPI-2W系列产品是专门针对PCB上需要与输入电源隔离的电源应用场合而设计的。该产品适用于&#xff1a;1&#xff09;输入电源的电压变化≤5%&#xff1b;2&#xff09;输入输出之间要求隔离电压≥3000VDC&#xff1b;3&#xff09;对输出电压稳定和输出纹波噪声要求高.

linux 安装openjdk-1.8

安装命令 yum install java-1.8.0-openjdk-1.8.0.262.b10-1.el7.x86_64查看安装路径 find / -name java 默认的安装路径 /usr/lib/jvm 查看到jre 以及java-1.8.0-openjdk-1.8.0.262.b10-1.el7.x86_64 配置环境变量 vim /etc/profile 添加的内容 export JAVA_HOME/usr/li…

网络工程师----第三天

HDLC帧格式及控制手段&#xff1a; ipv4到ipv6的过渡技术&#xff1a; 1、双栈技术&#xff1a;主机或路由器同时装有IPV4 和 IPV6两个协议栈&#xff0c;因此&#xff0c;主机既能和IPV4通信&#xff0c;也能和IPv6网络通信。IPv6和IPv4是功能相近的网络层协议&#xff0c;两者…

爱普生计时设备AUTOMOTIVE RA8900CE DTCXO RTC

主要特点出场已校准带有DTCXO的RTC&#xff0c;并且内部集成晶体单元高精度: 3.4 ppm 40 to 85 C(9 s/月.)时钟输出:1 Hz.1024 Hz.32.768 kHzI 2 C Interface: Fast mode (400 kHz)The l2C-Bus is a trademark ofNXP Semiconductors供电电压: 2.5-5.5 V(main),1.6-5.5 V(备份电…

SQL优化——统计信息

文章目录 1、统计信息1.1、表的统计信息1.2、列的统计信息1.3、索引的统计信息 2、统计信息重要参数设置3、检查统计信息是否过期4、扩展统计信息5、动态采样6、定制统计信息收集策略 只有大表才会产生性能问题&#xff0c;那么怎么才能让优化器知道某个表多大呢&#xff1f;这…

果园系统养殖游戏喂养偷菜种植浇水养成小程序

装扮 通过购买装扮场景切换不同的农场风格 土地升级 通过特定的材料对土地和房屋进行升级 日志 记录道具的使用数量及金币农作物的收入情况 幸运转盘 可用金币进行抽奖 宝箱开启 获得宝箱后可以通过金币开启 每日签到 每日签到获得奖励 系统公告 可以第一时间知道游戏的更新和…

Linux进阶--文本处理grep、sed、awk命令

目录 一、grep &#xff08;1&#xff09;用文件查找 二、正则表达式 三、sed命令 四、awk命令 grep、sed、awk可以称作linux里的三驾马车 一、grep grep&#xff1a;一种强大的文本搜索工具&#xff0c;它能使用正则表达式匹配模式搜 索文本&#xff0c;并把匹配的行打…

智能电视/盒子长文字输入困难?手把手教您解决这个难题!(电视盒子跨屏输入/打字,亲测有效!)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 解决方案 📒📝 方法一📝 方法二🎈 获取方式 🎈⚓️ 相关链接 ⚓️📖 介绍 📖 在使用智能电视/电视盒子的时候,会遇到这样一个场景:需要输入一个很长的网址,或者是想要粘贴一段很长的文字。如何使用遥控器要完成…

【C语言】qsort()函数排序及其模拟实现,万物皆可排!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 函数介绍 2. qsort举例排列整型变量 3. qsort举例排列结构型变量 3.1 按名字排序 3.1.1 srtcmp函数 3.2 按年龄排序 4. qsort函数模拟实现(采用冒泡的…

python怎么连接oracle

一&#xff1a;弄清版本&#xff0c;最重要&#xff01;&#xff01;&#xff01; 首先安装配置时&#xff0c;必须把握一个点&#xff0c;就是版本一致&#xff01;包括&#xff1a;系统版本&#xff0c;python版本&#xff0c;oracle客户端的版本&#xff0c;cx_Oracle的版本…

工作流 jbpm概述

文章目录 1 工作流概述2 jBPM概述3 jBPM开发环境搭建及其配置3.1 准备工作3.2 搭建jBPM开发环境3.3 加入jar包 总结 1 工作流概述 工作流&#xff08;Workflow&#xff09;&#xff0c;就是“业务过程的部分或整体在计算机应用环境下的自动化”&#xff0c;它主要解决的是“使…

STM32 CAN过滤器细节

STM32 CAN过滤器细节 简介 每组筛选器包含2个32位的寄存器&#xff0c;分别为CAN_FxR1和CAN_FxR2&#xff0c;它们用来存储要筛选的ID或掩码 四种模式 模式说明32位掩码模式CAN_FxR1存储ID&#xff0c; CAN_FxR2存储哪个位必须要与CAN_FxR1中的ID一致 &#xff0c; 2个寄存器…