多线程 | CompletableFutureAPI简单介绍

news2024/12/25 9:12:23

文章目录

  • 1. 案例
    • Future方案
    • CompletableFuture方案
  • 2. CompletableFuture方法一览
    • 使用new方法
    • supplyAsync方法
    • runAsync方法
    • 为什么仍需要CompletableFuture
    • 其他API介绍
      • whenComplete
      • thenApply
      • thenAccept
      • thenCompose
      • thenCombine
      • allOf&anyOf
      • supplyAsync
  • 3. 注意点
  • 4. 总结

本文从实例出发,介绍 CompletableFuture 基本用法。不过讲的再多,不如亲自上手练习一下。所以建议各位小伙伴看完,上机练习一把,快速掌握 CompletableFuture。

1. 案例

我们使用模拟个商品详情页接口写个demo

Future方案

一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。 Java 提供
RunnableFuture<V> 两个接口用来实现异步任务逻辑。
虽然 Future<V> 可以获取任务执行结果,但是获取方式十分不变。我们不得不使用 Future#get 阻塞调用线程,或者使用轮询方式判断 Future#isDone 任务是否结束,再获取结果。
这两种处理方式都不是很优雅,JDK8 之前并发类库没有提供相关的异步回调实现方式。没办法,我们只好借助第三方类库,如 Guava,扩展 Future,增加支持回调功能。相关代码如下:

ExecutorService executorService = Executors.newFixedThreadPool(10);
// Guava ListenableFutureTask
ListenableFutureTask<String> listenableFutureTask = ListenableFutureTask.create(() -> {
    sleep(2, TimeUnit.SECONDS) ;
    return "Future create";
});
//增加回消处理,注后这生要设置一个线程池参效哦
Futures.addCallback(listenableFutureTask, new FutureCallback<String>(){
    @Override 
    public void onSuccess(@NullableDecl String result) {
        System.out.println("处理返回结果:"+result);
    }
    @Override 
    public void onFailure(Throwable throwable){
        System.out.println("处理异常");
    }
},Executors.newSingleThreadScheduledExecutor()) ;
executorService.submit(listenableFutureTask);

虽然这种方式增强了 Java 异步编程能力,但是还是无法解决多个异步任务需要相互依赖的场景。
举一个生活上的例子,假如我们需要出去旅游,需要完成三个任务:
任务一:获取商品信息
任务二:获取促销信息
任务三:获取库存信息
很显然任务一和任务二没有相关性,可以单独执行。但是任务三必须等待任务一与任务二结束之后,才能获取库存服务。
为了使任务三时执行时能获取到任务一与任务二执行结果,我们还需要借助 CountDownLatch(下篇我们再介绍) 。

ExecutorService executorService = Executors.newFixedThreadPool( 10);
CountDownLatch countDownLatch = new CountDownLatch(2);
// 任务 1 获取商品
Future<String> productFuture = executorService.submit() -> {
    sleep(3, TimeUnit.SECONDS) ;
    System.out.println("获取商品");
    countDownLatch. countDown();
    return "商品";
});
// 任务 2 获取促销
Future<String> promotionFuture = executorService.submit(() -> {
    sleep(5, TimeUnit.SECONDS);
    System.out.println("获取促销");
    countDownLatch.countDown();
    return"促销";
});
// 使用 countDownLatch 等待任务 1 与任务2完成
countDownLatch.await();
Future<String> stockFuture = executorService. submit ( -> {
    System.out.println("根据商品里的skuId和促销信息获取库存");
    sleep(5, TimeUnit.SECONDS);
    return "库存";
});
System.out.println(orderCar.get());
/**
* 执行结果:
* 获取商品
* 商品
* 获取促销
* 促销
* 根据商品里的skuId和促销信息获取库存
* 库存
*/

CompletableFuture方案

JDK8 之后,Java 新增一个功能十分强大的类:CompletableFuture。单独使用这个类就可以轻松的完成上面的需求:

// 任务1 获取商品
CompletableFuture<String> productFuture = CompletableFuture. supplyAsync((0) -> {
    System.out.println("获取商品");
    Sleep(3, TimeUnit.SECONDS);
    return "商品";
});
//任务2 获取促销
CompletableFuture<String> promotionFuture = CompletableFuture.supplyAsync(() -> {
    sleep(5, TimeUnit.SECONDS) ;
    System.out.println("获取促销");
    return "促销";
});

//任务 3:任务 1 与任务 2 都完成后去获取库存
CompletableFutures<String> stockFuture = productFuture.thenCombine(promotionFuture,(product, promotion) ->{
    System.out.println("根据商品里的skuId和促销信息获取库存");
    Sleep(5, TimeUnit.SECONDS);
    return "库存";
});
//任务 3 执行结果
System.out.println(stockFuture.join());
/**
* 执行结果:
* 获取商品
* 商品
* 获取促销
* 促销
* 根据商品里的skuId和促销信息获取库存
* 库存
*/

对比 Future,CompletableFuture 优点在于:

  • 不需要手工分配线程,JDK 自动分配
  • 代码语义清晰,异步任务链式调用
  • 支持编排异步任务

2. CompletableFuture方法一览

在这里插入图片描述

使用new方法

CompletableFuture<Double> futurePrice = new CompletableFuture<>();使用CompletableFuture#completedFuture静态方法创建

public static <U> CompletableFuture<U> completedFuture(U value) {   
	return new CompletableFuture<U>((value == null) ? NIL : value);
}

参数的值为任务执行完的结果,一般该方法在实际应用中较少应用

supplyAsync方法

使用 CompletableFuture#supplyAsync静态方法创建 supplyAsync有两个重载方法:

//方法一
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}
//方法二
public static <U>  CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
}

runAsync方法

使用CompletableFuture#runAsync静态方法创建 runAsync有两个重载方法

//方法一
public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}
//方法二
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

两个重载方法之间的区别 => 后者可以传入自定义Executor,前者是默认的,使用的ForkJoinPool

supplyAsyncrunAsync方法之间的区别 => 前者有返回值,后者无返回值
Supplier是函数式接口,因此该方法需要传入该接口的实现类,追踪源码会发现在run方法中会调用该接口的方法。因此使用该方法创建CompletableFuture对象只需重写Supplier中的get方法,在get方法中定义任务即可。又因为函数式接口可以使用Lambda表达式,和new创建CompletableFuture对象相比代码会「简洁」不少

结果的获取:」 对于结果的获取CompltableFuture类提供了四种方式

//方式一
public T get()
//方式二
public T get(long timeout, TimeUnit unit)
//方式三
public T getNow(T valueIfAbsent)
//方式四
public T join()

说明:

  • get()和get(long timeout, TimeUnit unit) => 在Future中就已经提供了,后者提供超时处理,如果在指定时间内未获取结果将抛出超时异常
  • getNow => 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的valueIfAbsent值
  • join => 方法里不会抛出异常
    示例:
public class AcquireResultTest {
  public static void main(String[] args) throws ExecutionException, InterruptedException {
    //getNow方法测试
    CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {
      try
       {
          Thread.sleep(60 *  1000 *  60 );
       }  catch (InterruptedException e) {
          e.printStackTrace();
       }
 	   return "hello world";
 	});
	System.out.println(cp1.getNow("hello h2t"));
	//join方法测试
    CompletableFuture<Integer> cp2 = CompletableFuture.supplyAsync((()->1/0));
    System.out.println(cp2.join());
    //get方法测试
    CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((()->1/0));
    System.out.println(cp3.get());
   }
}

说明:

  • 第一个执行结果为hello h2t,因为要先睡上1分钟结果不能立即获取
  • join方法获取结果方法里不会抛异常,但是执行结果会抛异常,抛出的异常为CompletionException
  • get方法获取结果方法里将抛出异常,执行结果抛出的异常为ExecutionException
  • 「异常处理:」使用静态方法创建的CompletableFuture对象无需显示处理异常,使用new创建的对象需要调用completeExceptionally方法设置捕获到的异常,

举例说明:

CompletableFuture completableFuture = new CompletableFuture();
new Thread(() -> {
  try{
      //doSomething,调用complete方法将其他方法的执行结果记录在completableFuture对象中
       completableFuture.complete(null);
  } catch (Exception e) {
      //异常处理
      completableFuture.completeExceptionally(e);
  }
}).start();

同步方法Pick异步方法查询所有店铺某个商品价格
店铺为一个列表:

private static List<Shop> shopList = Arrays.asList(
        new Shop("BestPrice"),
        new Shop("LetsSaveBig"),
        new Shop("MyFavoriteShop"),
        new Shop("BuyItAll")
);

同步方法:

private static List<String> findPriceSync(String product) {
    return shopList.stream()
            .map(shop -> String.format("%s price is %.2f",
                    shop.getName(), shop.getPrice(product)))  //格式转换
            .collect(Collectors.toList());
}

异步方法:

private static List<String> findPriceAsync(String product) {
    List<CompletableFuture<String>> completableFutureList = shopList.stream()
            //转异步执行
            .map(shop -> CompletableFuture.supplyAsync(
                    () -> String.format("%s price is %.2f",
                            shop.getName(), shop.getPrice(product))))  //格式转换
            .collect(Collectors.toList());

    return completableFutureList.stream()
            .map(CompletableFuture::join)  //获取结果不会抛出异常
            .collect(Collectors.toList());
}

为什么仍需要CompletableFuture

对于简单的业务场景使用Future完全可以,但是想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值等等,使用Future提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以「声明式」的方式优雅的处理这些需求。而且在Future编程中想要拿到Future的值然后拿这个值去做后续的计算任务,只能通过轮询的方式去判断任务是否完成这样非常占CPU并且代码也不优雅,用伪代码表示如下:

while(future.isDone()) {
    result = future.get();
    doSomrthingWithResult(result);
} 

但CompletableFuture提供了API帮助我们实现这样的需求

其他API介绍

whenComplete

计算结果的处理:对前面计算结果进行处理,无法返回新值
提供了三个方法:

//方法一
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
//方法二
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
//方法三
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

说明:

  • BiFunction<? super T,? super U,? extends V> fn参数 => 定义对结果的处理
  • Executor executor参数 => 自定义线程池
  • 以async结尾的方法将会在一个新的线程中执行组合操作
    示例:
public class WhenCompleteTest {
    public static void main(String[] args) {
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "hello");
        CompletableFuture<String> cf2 = cf1.whenComplete((v, e) ->
                System.out.println(String.format("value:%s, exception:%s", v, e)));
        System.out.println(cf2.join());
    }
}

thenApply

将前面计算结果的的CompletableFuture传递给thenApply,返回thenApply处理后的结果。可以认为通过thenApply方法实现CompletableFuture至CompletableFuture的转换。白话一点就是将CompletableFuture的计算结果作为thenApply方法的参数,返回thenApply方法处理后的结果
提供了三个方法


//方法一
public <U> CompletableFuture<U> thenApply(
    Function<? super T,? extends U> fn) {
    return uniApplyStage(null, fn);
}

//方法二
public <U> CompletableFuture<U> thenApplyAsync(
    Function<? super T,? extends U> fn) {
    return uniApplyStage(asyncPool, fn);
}

//方法三
public <U> CompletableFuture<U> thenApplyAsync(
    Function<? super T,? extends U> fn, Executor executor) {
    return uniApplyStage(screenExecutor(executor), fn);
}

说明:

  • Function<? super T,? extends U> fn参数 => 对前一个CompletableFuture 计算结果的转化操作
  • Executor executor参数 => 自定义线程池
  • 以async结尾的方法将会在一个新的线程中执行组合操作 示例:
public class ThenApplyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenApplyTest::randomInteger).thenApply((i) -> i * 8);
        System.out.println(result.get());
    }

    public static Integer randomInteger() {
        return 10;
    }
}

这里将前一个CompletableFuture计算出来的结果扩大八倍

thenAccept

thenApply也可以归类为对结果的处理,thenAccept和thenApply的区别就是没有返回值
提供了三个方法:

//方法一
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
    return uniAcceptStage(null, action);
}

//方法二
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
    return uniAcceptStage(asyncPool, action);
}

//方法三
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
                                               Executor executor) {
    return uniAcceptStage(screenExecutor(executor), action);
}

说明:

  • Consumer<? super T> action参数 => 对前一个CompletableFuture计算结果的操作
  • Executor executor参数 => 自定义线程池
  • 同理以async结尾的方法将会在一个新的线程中执行组合操作 示例:
public class ThenAcceptTest {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(ThenAcceptTest::getList).thenAccept(strList -> strList.stream()
                .forEach(m -> System.out.println(m)));
    }

    public static List<String> getList() {
        return Arrays.asList("a", "b", "c");
    }
}

将前一个CompletableFuture计算出来的结果打印出来

thenCompose

异步结果流水化,可以将两个异步操作进行流水操作
提供了三个方法:

//方法一
public <U> CompletableFuture<U> thenCompose(
    Function<? super T, ? extends CompletionStage<U>> fn) {
    return uniComposeStage(null, fn);
}

//方法二
public <U> CompletableFuture<U> thenComposeAsync(
    Function<? super T, ? extends CompletionStage<U>> fn) {
    return uniComposeStage(asyncPool, fn);
}

//方法三
public <U> CompletableFuture<U> thenComposeAsync(
    Function<? super T, ? extends CompletionStage<U>> fn,
    Executor executor) {
    return uniComposeStage(screenExecutor(executor), fn);
}

说明:

  • Function<? super T, ? extends CompletionStage<?>> fn参数 => 当前CompletableFuture计算结果的执行
  • Executor executor参数 => 自定义线程池
  • 同理以async结尾的方法将会在一个新的线程中执行组合操作
    示例:
public class ThenComposeTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenComposeTest::getInteger)
                .thenCompose(i -> CompletableFuture.supplyAsync(() -> i * 10));
        System.out.println(result.get());
    }

    private static int getInteger() {
        return 666;
    }

    private static int expandValue(int num) {
        return num * 10;
    }
}

thenCombine

thenCombine方法将两个无关的CompletableFuture组合起来,第二个Completable并不依赖第一个Completable的结果
提供了三个方法:

//方法一
public <U,V> CompletableFuture<V> thenCombine( 
    CompletionStage<? extends U> other,
    BiFunction<? super T,? super U,? extends V> fn) {
    return biApplyStage(null, other, fn);
}
  //方法二
  public <U,V> CompletableFuture<V> thenCombineAsync(
      CompletionStage<? extends U> other,
      BiFunction<? super T,? super U,? extends V> fn) {
      return biApplyStage(asyncPool, other, fn);
  }

  //方法三
  public <U,V> CompletableFuture<V> thenCombineAsync(
      CompletionStage<? extends U> other,
      BiFunction<? super T,? super U,? extends V> fn, Executor executor) {
      return biApplyStage(screenExecutor(executor), other, fn);
  }

说明:

  • CompletionStage<? extends U> other参数 => 新的CompletableFuture的计算结果
  • BiFunction<? super T,? super U,? extends V> fn参数 => 定义了两个CompletableFuture对象「完成计算后」如何合并结果,该参数是一个函数式接口,因此可以使用Lambda表达式
  • Executor executor参数 => 自定义线程池
  • 同理以async结尾的方法将会在一个新的线程中执行组合操作

示例:

public class ThenCombineTest {
    private static Random random = new Random();
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> result = CompletableFuture.supplyAsync(ThenCombineTest::randomInteger).thenCombine(
                CompletableFuture.supplyAsync(ThenCombineTest::randomInteger), (i, j) -> i * j
        );

        System.out.println(result.get());
    }

    public static Integer randomInteger() {
        return random.nextInt(100);
    }
}

allOf&anyOf

//allOf
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
    return andTree(cfs, 0, cfs.length - 1);
}
//anyOf
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
    return orTree(cfs, 0, cfs.length - 1);
}

说明:

  • allOf => 所有的CompletableFuture都执行完后执行计算。
  • anyOf => 任意一个CompletableFuture执行完后就会执行计算

示例:allOf方法测试

public class AllOfTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("hello");
            return null;
        });
        CompletableFuture<Void> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("world"); return null;
        });
        CompletableFuture<Void> result = CompletableFuture.allOf(future1, future2);
        System.out.println(result.get());
    }
}

allOf方法没有返回值,适合没有返回值并且需要前面所有任务执行完毕才能执行后续任务的应用场景

public class AnyOfTest {
    private static Random random = new Random();
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            randomSleep();
            System.out.println("hello");
            return "hello";});
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            randomSleep();
            System.out.println("world");
            return "world";
        });
        CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);
        System.out.println(result.get());
   }

    private static void randomSleep() {
        try {
            Thread.sleep(random.nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

两个线程都会将结果打印出来,但是get方法只会返回最先完成任务的结果。该方法比较适合只要有一个返回值就可以继续执行其他任务的应用场景

supplyAsync

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e, Supplier<U> f) {
    if (f == null) throw new NullPointerException();
    CompletableFuture<U> d = new CompletableFuture<U>();
    e.execute(new AsyncSupply<U>(d, f));
    return d;
}

底层调用的是线程池去执行任务,而CompletableFuture中默认线程池为ForkJoinPool

private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

ForkJoinPool线程池的大小取决于CPU的核数。CPU密集型任务线程池大小配置为CPU核心数就可以了,但是IO密集型,线程池的大小由
CPU数量 * CPU利用率 * (1 + 线程等待时间/线程CPU时间)
确定。而CompletableFuture的应用场景就是IO密集型任务,因此默认的ForkJoinPool一般无法达到最佳性能,我们需自己根据业务创建线程池。

3. 注意点

很多方法都提供了异步实现【带async后缀】,但是需小心谨慎使用这些异步方法,因为异步意味着存在上下文切换,可能性能不一定比同步好。如果需要使用异步的方法,「先做测试」,用测试数据说话!!!

4. 总结

本次只是介绍CompletableFuture的常用方法介绍,具体还需要结合根据自己业务代码进行使用。

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

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

相关文章

游戏厅计费系统电玩计时系统软件 佳易王计时定时语音提醒管理系统操作教程

一、前言 游戏厅计费系统电玩计时系统软件 佳易王计时定时语音提醒管理系统操作教程 1、佳易王电玩店ps5计时计费软件&#xff0c;可以设置定时语音提醒。 2、软件为免安装版&#xff0c;解压即可。 二、软件程序部分功能图文说明 1、在设置定时语音提醒的时候&#xff0c;只…

第49课 Scratch入门篇:骇客任务背景特效

骇客任务背景特效 故事背景: 骇客帝国特色背景在黑色中慢慢滚动着! 程序原理: 1 、 角色的设计技巧  2 、克隆体的应用及特效的使用 开始编程 1、使用 黑色的背景: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7d74c872f06b4d9fbc88aecee634b074.png#pic_c…

以太坊入门

1. 以太坊简介 Vitalik Buterin 在 2013 年 11 月提出了以太坊的概念&#xff0c;其关键思想是&#xff1a;开发一种图灵完备&#xff08;Turing-Complete) 的语言&#xff0c;以允许开发用于区块链和去中心化应用的任意程序&#xff08;智能合约&#xff09;。该概念与比特比相…

驱动(RK3588S)第七课时:单节点设备树

目录 需求一、设备树的概念1、设备树的后缀名&#xff1a;2、设备树的语法格式3、设备树的属性&#xff08;重要&#xff09;4、设备树格式举例 二、设备树所用函数1、如何在内核层种获取设备树节点&#xff1a;2、从设备树上获取 gpio 口的属性3、获取节点上的属性只针对于字符…

【0324】Postgres内核 Shared Buffer Access Rules (共享缓冲区访问规则)说明

0. 章节内容 1. 共享磁盘缓冲区访问机制 (shared disk buffers) 共享磁盘缓冲区有两套独立的访问控制机制:引用计数(a/k/a pin 计数)和缓冲区内容锁。(实际上,还有第三级访问控制:在访问任何属于某个关系表的页面之前,必须持有该关系表的适当类型的锁。这里不讨论关系…

[Linux Kernel Block Layer第一篇] block layer架构设计

目录 1. single queue架构 2. multi-queue架构&#xff08;blk-mq) 3. 问题 随着SSD快速存储设备的发展&#xff0c;内核社区越发发现&#xff0c;存储的性能瓶颈从硬件存储设备转移到了内核block layer&#xff0c;主要因为当时的内核block layer是single hw queue的架构&…

【C语言】插入排序、希尔排序——动图展示

目录 1. 插入排序1.1 基本概念1.2 实现思路1.3 代码部分 2. 希尔排序2.1 为什么会有希尔排序&#xff1f;2.2 基本概念2.3 实现思想1&#xff09;单组排序2&#xff09;多组排序 2.4 代码部分 3. 总结 1. 插入排序 1.1 基本概念 把待排序的记录逐个插入到一个已经排好序的有序…

PMP–一、二、三模–分类–14.敏捷–技巧–看板面板与燃尽图燃起图

文章目录 技巧一模14.敏捷--方法--看板&#xff08;类似卡片&#xff09;1、 [单选] 根据项目的特点&#xff0c;项目经理建议选择一种敏捷方法&#xff0c;该方法限制团队成员在任何给定时间执行的任务数。此方法还允许团队提高工作过程中问题和瓶颈的可见性。项目经理建议采用…

微软出品的一款管理多个远程桌面连接的工具

RDCMan&#xff08;Remote Desktop Connection Manager&#xff09;是微软官方出品的一款用于管理多个远程桌面连接的工具。它可以帮助用户集中管理和分类远程桌面&#xff0c;特别适用于需要同时管理大量服务器或在不同计算机间切换操作的场景。 RDCMan的主要功能包括&#x…

使用WebP解决网站加载速度问题,这些细节你需要了解

说到网页的图片格式,大家最常想到的可能是JPEG、PNG,毕竟这些老牌格式陪伴我们这么多年。然而,近几年,有一个格式悄悄崭露头角,那就是WebP。很多人可能听说过,但到底它好在哪?你的网站或者项目是不是也应该用WebP呢?别着急,今天咱们就来好好聊聊WebP这个图片格式的前世…

SOMEIP_ETS_095: SD_Check_subscribe_eventgroup_ttl_expired

测试目的&#xff1a; 验证DUT&#xff08;Device Under Test&#xff09;能够检测到测试器&#xff08;Tester&#xff09;的订阅已过期&#xff08;ttl 3秒&#xff09;&#xff0c;并且在TTL过期后不响应测试器触发的事件。 描述 本测试用例旨在确保DUT能够识别测试器的…

Kettle使用命令pan/kitchen执行任务时传参问题

在用windows任务执行kettle的kjb或ktr文件时&#xff0c;可通过bat命令传递参数&#xff0c;测试了很久&#xff0c;特此记录一下。 一、pan\kitchen 参数说明 Options: /rep : Repository name /user : Repository username /pass : Repository password…

【射频通信电路基础第二讲】射频通信电路基础知识——射频接插件、金属导线的趋肤效应、射频传输线及其特性、衰减电路等

一、射频接插件 参考https://blog.csdn.net/weixin_43813325/article/details/112340937 1、BNC&#xff1a;阻抗一般为50/75Ω&#xff0c;频带宽 2、SMA/SMB&#xff1a;损耗小&#xff0c;价格高昂 SMA接口有两种形式&#xff0c;分别如下所示&#xff0c;常规SMA“外螺纹…

AT32F415的OTA升级

AT32F415的OTA升级 项目简介IAP实现原理AT32中的内置FLASH分配情况AT32中的实现过程跳转到APP的程序代码删除APP区域的FLASH空间代码写APP的bin数据到FLASH空间代码 项目简介 在物联网应用开发过程中&#xff0c;不可避免的会需要用到软件升级&#xff0c;一般情况下&#xff…

C# Hash算法之MD5、SHA

MD5我们用的还是比较多的&#xff0c;一般用来加密存储密码。但是现在很多人觉MD5可能不太安全了&#xff0c;所以都用上了SHA256等来做加密&#xff08;虽然我觉得都差不多,MD5还是能玩&#xff09;。 还是跟上一篇说的一样&#xff0c;当一个算法的复杂度提高的同时肯定会带…

linux dlopen手册翻译

名称 dlclose, dlopen, dlmopen 打开和关闭一个共享对象 简介 #include <dlfcn.h> void *dlopen(const char*filename, int flags); int dlclose(void *handle);#define _GNU_SOURCE #include <dlfcn.h> void *dlmoopen(Lmid_t lmid, const char *filename, int…

【C++ Primer Plus习题】12.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream> #include "Stack.h"…

磁电偶极子天线学习1 一种60GHz 宽带圆极化口径耦合磁电偶极子天线阵列

摘要&#xff1a; 一种新型的圆极化口径耦合天线被提出。这种圆极化磁电偶极子天线由刻蚀在短路基片集成波导的一部分的宽臂上&#xff0c;并且很容易被集成基片。在工作频段内实现了宽于28.8%的阻抗带宽和宽带3-dB的25.9%的轴比和的增益。此外&#xff0c;因为圆极化辐射由两个…

稀有卡牌手游【植物大战僵尸】源码

稀有卡牌手游【植物大战僵尸】完整代码&#xff0c;画面精美&#xff0c;非常好玩。代码下载

机器学习1——手把手教你用Python跑一个线性回归模型

目录 一、前期准备 1.Scikit-learn 2.matplotlib 二、机器学习过程 三、代码框架 四、完整代码 1.导入所需库 2.准备训练数据 3.喂入训练数据 4.结果预测 5.输出模型中的w与b值 6.可视化 7.传入不规则数据 一、前期准备 在机器学习中我们使用Python居多&…