Reactor详解
目录
1、快速上手
介绍
2、响应式编程
2.1. 阻塞是对资源的浪费
2.2. 异步可以解决问题吗?
2.3.1. 可编排性与可读性
2.3.2. 就像装配流水线
2.3.3. 操作符(Operators)
2.3.4. subscribe() 之前什么都不会发生
2.3.5. 背压
2.3.6. 热(Hot) vs 冷(Cold)
3、核心特性
1、Mono和Flux
2、subscribe()
3、流的取消
Disposable
4、BaseSubscriber
5、背压(Backpressure )和请求重塑(Reshape Requests)
1、buffer:缓冲
2、limit:限流
6、以编程方式创建序列-Sink
1、同步环境-generate
2、多线程-create
7、 handle()
8、自定义线程调度
9、错误处理
1. Catch and return a static default value. 捕获异常返回一个静态默认值
2. Catch and execute an alternative path with a fallback method.
3. Catch and dynamically compute a fallback value. 捕获并动态计算一个返回值
4. Catch, wrap to a BusinessException, and re-throw.
5. Catch, log an error-specific message, and re-throw.
6. Use the finally block to clean up resources or a Java 7 “try-with-resource” construct.
7. 忽略当前异常,仅通知记录,继续推进
10:常用操作
1、快速上手
介绍
Reactor 是一个用于JVM的完全非阻塞的响应式编程框架,具备高效的需求管理(即对 “背压(backpressure)”的控制)能力。它与 Java 8 函数式 API 直接集成,比如 CompletableFuture, Stream, 以及 Duration。它提供了异步序列 API Flux(用于[N]个元素)和 Mono(用于 [0|1]个元素),并完全遵循和实现了“响应式扩展规范”(Reactive Extensions Specification)。
Reactor 的 reactor-ipc 组件还支持非阻塞的进程间通信(inter-process communication, IPC)。 Reactor IPC 为 HTTP(包括 Websockets)、TCP 和 UDP 提供了支持背压的网络引擎,从而适合 应用于微服务架构。并且完整支持响应式编解码(reactive encoding and decoding)。
<dependencyManagement> <dependencies><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-bom</artifactId><version>2023.0.0</version><type>pom</type><scope>import</scope></dependency></dependencies>
</dependencyManagement>
<dependencies><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId> </dependency><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-test</artifactId> <scope>test</scope></dependency>
</dependencies>
2、响应式编程
响应式编程是一种关注于数据流(data streams)和变化传递(propagation of change)的异步编程方式。 这意味着它可以用既有的编程语言表达静态(如数组)或动态(如事件源)的数据流。
了解历史:
-
在响应式编程方面,微软跨出了第一步,它在 .NET 生态中创建了响应式扩展库(Reactive Extensions library, Rx)。接着 RxJava 在JVM上实现了响应式编程。后来,在 JVM 平台出现了一套标准的响应式 编程规范,它定义了一系列标准接口和交互规范。并整合到 Java 9 中(使用 Flow 类)。
-
响应式编程通常作为面向对象编程中的“观察者模式”(Observer design pattern)的一种扩展。 响应式流(reactive streams)与“迭代子模式”(Iterator design pattern)也有相通之处, 因为其中也有 Iterable-Iterator 这样的对应关系。主要的区别在于,Iterator 是基于 “拉取”(pull)方式的,而响应式流是基于“推送”(push)方式的。
-
使用 iterator 是一种“命令式”(imperative)编程范式,即使访问元素的方法是 Iterable 的唯一职责。关键在于,什么时候执行 next() 获取元素取决于开发者。在响应式流中,相对应的 角色是 Publisher-Subscriber,但是 当有新的值到来的时候 ,却反过来由发布者(Publisher) 通知订阅者(Subscriber),这种“推送”模式是响应式的关键。此外,对推送来的数据的操作 是通过一种声明式(declaratively)而不是命令式(imperatively)的方式表达的:开发者通过 描述“控制流程”来定义对数据流的处理逻辑。
-
除了数据推送,对错误处理(error handling)和完成(completion)信号的定义也很完善。 一个 Publisher 可以推送新的值到它的 Subscriber(调用 onNext 方法), 同样也可以推送错误(调用 onError 方法)和完成(调用 onComplete 方法)信号。 错误和完成信号都可以终止响应式流。可以用下边的表达式描述:
-
onNext x 0..N [onError | onComplete]
2.1. 阻塞是对资源的浪费
现代应用需要应对大量的并发用户,而且即使现代硬件的处理能力飞速发展,软件性能仍然是关键因素。
广义来说我们有两种思路来提升程序性能:
-
并行化(parallelize) :使用更多的线程和硬件资源。[异步]
-
基于现有的资源来 提高执行效率 。
通常,Java开发者使用阻塞式(blocking)编写代码。这没有问题,在出现性能瓶颈后, 我们可以增加处理线程,线程中同样是阻塞的代码。但是这种使用资源的方式会迅速面临 资源竞争和并发问题。
更糟糕的是,阻塞会浪费资源。具体来说,比如当一个程序面临延迟(通常是I/O方面, 比如数据库读写请求或网络调用),所在线程需要进入 idle 状态等待数据,从而浪费资源。
所以,并行化方式并非银弹。这是挖掘硬件潜力的方式,但是却带来了复杂性,而且容易造成浪费。
2.2. 异步可以解决问题吗?
第二种思路——提高执行效率——可以解决资源浪费问题。通过编写 异步非阻塞 的代码, (任务发起异步调用后)执行过程会切换到另一个 使用同样底层资源 的活跃任务,然后等 异步调用返回结果再去处理。
但是在 JVM 上如何编写异步代码呢?Java 提供了两种异步编程方式:
-
回调(Callbacks) :异步方法没有返回值,而是采用一个 callback 作为参数(lambda 或匿名类),当结果出来后回调这个 callback。常见的例子比如 Swings 的 EventListener。
-
Futures :异步方法 立即 返回一个 Future<T>,该异步方法要返回结果的是 T 类型,通过 Future封装。这个结果并不是 立刻 可以拿到,而是等实际处理结束才可用。比如, ExecutorService 执行 Callable<T> 任务时会返回 Future 对象。
这些技术够用吗?并非对于每个用例都是如此,两种方式都有局限性。
回调很难组合起来,因为很快就会导致代码难以理解和维护(即所谓的“回调地狱(callback hell)”)。
考虑这样一种情景:
-
在用户界面上显示用户的5个收藏,或者如果没有任何收藏提供5个建议。
-
这需要3个 服务(一个提供收藏的ID列表,第二个服务获取收藏内容,第三个提供建议内容):
回调地狱(Callback Hell)的例子:
userService.getFavorites(userId, new Callback<List<String>>() { public void onSuccess(List<String> list) { if (list.isEmpty()) { suggestionService.getSuggestions(new Callback<List<Favorite>>() {public void onSuccess(List<Favorite> list) { UiUtils.submitOnUiThread(() -> { list.stream().limit(5).forEach(uiList::show); });}public void onError(Throwable error) { UiUtils.errorPopup(error);}});} else {list.stream() .limit(5).forEach(favId -> favoriteService.getDetails(favId, new Callback<Favorite>() {public void onSuccess(Favorite details) {UiUtils.submitOnUiThread(() -> uiList.show(details));}public void onError(Throwable error) {UiUtils.errorPopup(error);}}));}}public void onError(Throwable error) {UiUtils.errorPopup(error);}
});
Reactor改造后为
userService.getFavorites(userId) .flatMap(favoriteService::getDetails) .switchIfEmpty(suggestionService.getSuggestions()) .take(5) .publishOn(UiUtils.uiThreadScheduler()) .subscribe(uiList::show, UiUtils::errorPopup);
如果你想确保“收藏的ID”的数据在800ms内获得(如果超时,从缓存中获取)呢?在基于回调的代码中, 会比较复杂。但 Reactor 中就很简单,在处理链中增加一个 timeout 的操作符即可。
userService.getFavorites(userId).timeout(Duration.ofMillis(800)) .onErrorResume(cacheService.cachedFavoritesFor(userId)) .flatMap(favoriteService::getDetails) .switchIfEmpty(suggestionService.getSuggestions()).take(5).publishOn(UiUtils.uiThreadScheduler()).subscribe(uiList::show, UiUtils::errorPopup);
-
背压(backpressure) 具体来说即 消费者能够反向告知生产者生产内容的速度的能力
-
高层次 (同时也是有高价值的)的抽象,从而达到 并发无关 的效果
2.3.1. 可编排性与可读性
可编排性,指的是编排多个异步任务的能力。比如我们将前一个任务的结果传递给后一个任务作为输入, 或者将多个任务以分解再汇总(fork-join)的形式执行,或者将异步的任务作为离散的组件在系统中 进行重用。
这种编排任务的能力与代码的可读性和可维护性是紧密相关的。随着异步处理任务数量和复杂度 的提高,编写和阅读代码都变得越来越困难。就像我们刚才看到的,回调模式是简单的,但是缺点 是在复杂的处理逻辑中,回调中会层层嵌入回调,导致 回调地狱(Callback Hell) 。你能猜到 (或有过这种痛苦经历),这样的代码是难以阅读和分析的。
Reactor 提供了丰富的编排操作,从而代码直观反映了处理流程,并且所有的操作保持在同一层次 (尽量避免了嵌套)。
2.3.2. 就像装配流水线
你可以想象数据在响应式应用中的处理,就像流过一条装配流水线。Reactor 既是传送带, 又是一个个的装配工或机器人。原材料从源头(最初的 Publisher)流出,最终被加工为成品, 等待被推送到消费者(或者说 Subscriber)。
原材料会经过不同的中间处理过程,或者作为半成品与其他半成品进行组装。如果某处有齿轮卡住, 或者某件产品的包装过程花费了太久时间,相应的工位就可以向上游发出信号来限制或停止发出原材料。
2.3.3. 操作符(Operators)
在 Reactor 中,操作符(operator)就像装配线中的工位(操作员或装配机器人)。每一个操作符 对 Publisher 进行相应的处理,然后将 Publisher 包装为一个新的 Publisher。就像一个链条, 数据源自第一个 Publisher,然后顺链条而下,在每个环节进行相应的处理。最终,一个订阅者 (Subscriber)终结这个过程。请记住,在订阅者(Subscriber)订阅(subscribe)到一个 发布者(Publisher)之前,什么都不会发生。
理解了操作符会创建新的 Publisher 实例这一点,能够帮助你避免一个常见的问题, 这种问题会让你觉得处理链上的某个操作符没有起作用。
虽然响应式流规范(Reactive Streams specification)没有规定任何操作符, 类似 Reactor 这样的响应式库所带来的最大附加价值之一就是提供丰富的操作符。包括基础的转换操作, 到过滤操作,甚至复杂的编排和错误处理操作。
2.3.4. subscribe() 之前什么都不会发生
在 Reactor 中,当你创建了一条 Publisher 处理链,数据还不会开始生成。事实上,你是创建了 一种抽象的对于异步处理流程的描述(从而方便重用和组装)。
当真正“订阅(subscrib)”的时候,你需要将 Publisher 关联到一个 Subscriber 上,然后 才会触发整个链的流动。这时候,Subscriber 会向上游发送一个 request 信号,一直到达源头 的 Publisher。
2.3.5. 背压
向上游传递信号这一点也被用于实现 背压 ,就像在装配线上,某个工位的处理速度如果慢于流水线 速度,会对上游发送反馈信号一样。
在响应式流规范中实际定义的机制同刚才的类比非常接近:订阅者可以无限接受数据并让它的源头 “满负荷”推送所有的数据,也可以通过使用 request 机制来告知源头它一次最多能够处理 n 个元素。
中间环节的操作也可以影响 request。想象一个能够将每10个元素分批打包的缓存(buffer)操作。 如果订阅者请求一个元素,那么对于源头来说可以生成10个元素。此外预取策略也可以使用了, 比如在订阅前预先生成元素。
这样能够将“推送”模式转换为“推送+拉取”混合的模式,如果下游准备好了,可以从上游拉取 n 个元素;但是如果上游元素还没有准备好,下游还是要等待上游的推送。
2.3.6. 热(Hot) vs 冷(Cold)
在 Rx 家族的响应式库中,响应式流分为“热”和“冷”两种类型,区别主要在于响应式流如何 对订阅者进行响应:
-
一个“冷”的序列,指对于每一个 Subscriber,都会收到从头开始所有的数据。如果源头 生成了一个 HTTP 请求,对于每一个订阅都会创建一个新的 HTTP 请求。
-
一个“热”的序列,指对于一个 Subscriber,只能获取从它开始 订阅 之后 发出的数据。不过注意,有些“热”的响应式流可以缓存部分或全部历史数据。 通常意义上来说,一个“热”的响应式流,甚至在即使没有订阅者接收数据的情况下,也可以 发出数据(这一点同 “Subscribe() 之前什么都不会发生”的规则有冲突)。
3、核心特性
1、Mono和Flux
Mono: 0|1 数据流
Flux: N数据流
响应式流:元素(内容) + 信号(完成/异常);
2、subscribe()
自定义流的信号感知回调
flux.subscribe(v-> System.out.println("v = " + v), //流元素消费throwable -> System.out.println("throwable = " + throwable), //感知异常结束()-> System.out.println("流结束了...") //感知正常结束
);
自定义消费者
flux.subscribe(new BaseSubscriber<String>() {
// 生命周期钩子1: 订阅关系绑定的时候触发@Overrideprotected void hookOnSubscribe(Subscription subscription) {// 流被订阅的时候触发System.out.println("绑定了..."+subscription);
//找发布者要数据request(1); //要1个数据
// requestUnbounded(); //要无限数据}
@Overrideprotected void hookOnNext(String value) {System.out.println("数据到达,正在处理:"+value);request(1); //要1个数据}
// hookOnComplete、hookOnError 二选一执行@Overrideprotected void hookOnComplete() {System.out.println("流正常结束...");}
@Overrideprotected void hookOnError(Throwable throwable) {System.out.println("流异常..."+throwable);}
@Overrideprotected void hookOnCancel() {System.out.println("流被取消...");}
@Overrideprotected void hookFinally(SignalType type) {System.out.println("最终回调...一定会被执行");}});
3、流的取消
消费者调用 cancle() 取消流的订阅;
Disposable
Flux<String> flux = Flux.range(1, 10).map(i -> {System.out.println("map..."+i);if(i==9) {i = 10/(9-i); //数学运算异常; doOnXxx}return "哈哈:" + i;}); //流错误的时候,把错误吃掉,转为正常信号
// flux.subscribe(); //流被订阅; 默认订阅;
// flux.subscribe(v-> System.out.println("v = " + v));//指定订阅规则: 正常消费者:只消费正常元素
// flux.subscribe(
// v-> System.out.println("v = " + v), //流元素消费
// throwable -> System.out.println("throwable = " + throwable), //感知异常结束
// ()-> System.out.println("流结束了...") //感知正常结束
// );
// 流的生命周期钩子可以传播给订阅者。// a() {// data = b();// }flux.subscribe(new BaseSubscriber<String>() {
// 生命周期钩子1: 订阅关系绑定的时候触发@Overrideprotected void hookOnSubscribe(Subscription subscription) {// 流被订阅的时候触发System.out.println("绑定了..."+subscription);
//找发布者要数据request(1); //要1个数据
// requestUnbounded(); //要无限数据}
@Overrideprotected void hookOnNext(String value) {System.out.println("数据到达,正在处理:"+value);if(value.equals("哈哈:5")){cancel(); //取消流}request(1); //要1个数据}
// hookOnComplete、hookOnError 二选一执行@Overrideprotected void hookOnComplete() {System.out.println("流正常结束...");}
@Overrideprotected void hookOnError(Throwable throwable) {System.out.println("流异常..."+throwable);}
@Overrideprotected void hookOnCancel() {System.out.println("流被取消...");}
@Overrideprotected void hookFinally(SignalType type) {System.out.println("最终回调...一定会被执行");}});
4、BaseSubscriber
自定义消费者,推荐直接编写 BaseSubscriber 的逻辑;
5、背压(Backpressure )和请求重塑(Reshape Requests)
1、buffer:缓冲
Flux<List<Integer>> flux = Flux.range(1, 10) //原始流10个.buffer(3).log();//缓冲区:缓冲3个元素: 消费一次最多可以拿到三个元素; 凑满数批量发给消费者
//
// //一次发一个,一个一个发;
// 10元素,buffer(3);消费者请求4次,数据消费完成
2、limit:限流
Flux.range(1, 1000).log()//限流触发,看上游是怎么限流获取数据的.limitRate(100) //一次预取30个元素; 第一次 request(100),以后request(75).subscribe();
6、以编程方式创建序列-Sink
Sink.next
Sink.complete
1、同步环境-generate
2、多线程-create
7、 handle()
自定义流中元素处理规则
//Flux.range(1,10).handle((value,sink)->{System.out.println("拿到的值:"+value);sink.next("张三:"+value); //可以向下发送数据的通道}).log() //日志.subscribe();
8、自定义线程调度
响应式:响应式编程: 全异步、消息、事件回调
默认还是用当前线程,生成整个流、发布流、流操作
public void thread1(){Scheduler s = Schedulers.newParallel("parallel-scheduler", 4);
final Flux<String> flux = Flux.range(1, 2).map(i -> 10 + i).log().publishOn(s).map(i -> "value " + i);
//只要不指定线程池,默认发布者用的线程就是订阅者的线程;new Thread(() -> flux.subscribe(System.out::println)).start();
}
9、错误处理
命令式编程:常见的错误处理方式
1. Catch and return a static default value. 捕获异常返回一个静态默认值
try {return doSomethingDangerous(10);
}
catch (Throwable error) {return "RECOVERED";
}
onErrorReturn: 实现上面效果,错误的时候返回一个值
-
1、吃掉异常,消费者无异常感知
-
2、返回一个兜底默认值
-
3、流正常完成;
Flux.just(1, 2, 0, 4).map(i -> "100 / " + i + " = " + (100 / i)).onErrorReturn(NullPointerException.class,"哈哈-6666").subscribe(v-> System.out.println("v = " + v),err -> System.out.println("err = " + err),()-> System.out.println("流结束")); // error handling example
2. Catch and execute an alternative path with a fallback method.
吃掉异常,执行一个兜底方法;
try {return doSomethingDangerous(10);
}
catch (Throwable error) {return doOtherthing(10);
}
onErrorResume
-
1、吃掉异常,消费者无异常感知
-
2、调用一个兜底方法
-
3、流正常完成
Flux.just(1, 2, 0, 4).map(i -> "100 / " + i + " = " + (100 / i)).onErrorResume(err -> Mono.just("哈哈-777")).subscribe(v -> System.out.println("v = " + v),err -> System.out.println("err = " + err),() -> System.out.println("流结束"));
3. Catch and dynamically compute a fallback value. 捕获并动态计算一个返回值
根据错误返回一个新值
try {Value v = erroringMethod();return MyWrapper.fromValue(v);
}
catch (Throwable error) {return MyWrapper.fromError(error);
}
.onErrorResume(err -> Flux.error(new BusinessException(err.getMessage()+":炸了")))
4. Catch, wrap to a BusinessException, and re-throw.
捕获并包装成一个业务异常,并重新抛出
try {return callExternalService(k);
}
catch (Throwable error) {throw new BusinessException("oops, SLA exceeded", error);
}
包装重新抛出异常: 推荐用 .onErrorMap
-
1、吃掉异常,消费者有感知
-
2、抛新异常
-
3、流异常完成
.onErrorResume(err -> Flux.error(new BusinessException(err.getMessage()+":炸了")))Flux.just(1, 2, 0, 4).map(i -> "100 / " + i + " = " + (100 / i)).onErrorMap(err-> new BusinessException(err.getMessage()+": 又炸了...")).subscribe(v -> System.out.println("v = " + v),err -> System.out.println("err = " + err),() -> System.out.println("流结束"));
5. Catch, log an error-specific message, and re-throw.
捕获异常,记录特殊的错误日志,重新抛出
try {
return callExternalService(k);
}
catch (RuntimeException error) {
//make a record of the error
log("uh oh, falling back, service failed for key " + k);
throw error;
}Flux.just(1, 2, 0, 4)
• .map(i -> "100 / " + i + " = " + (100 / i))
• .doOnError(err -> {
• System.out.println("err已被记录 = " + err);
• }).subscribe(v -> System.out.println("v = " + v),
• err -> System.out.println("err = " + err),
• () -> System.out.println("流结束"));
-
异常被捕获、做自己的事情
-
不影响异常继续顺着流水线传播
-
1、不吃掉异常,只在异常发生的时候做一件事,消费者有感知
6. Use the finally block to clean up resources or a Java 7 “try-with-resource” construct.
Flux.just(1, 2, 3, 4).map(i -> "100 / " + i + " = " + (100 / i)).doOnError(err -> {System.out.println("err已被记录 = " + err);}).doFinally(signalType -> {System.out.println("流信号:"+signalType);})
7. 忽略当前异常,仅通知记录,继续推进
Flux.just(1,2,3,0,5).map(i->10/i).onErrorContinue((err,val)->{System.out.println("err = " + err);System.out.println("val = " + val);System.out.println("发现"+val+"有问题了,继续执行其他的,我会记录这个问题");}) //发生.subscribe(v-> System.out.println("v = " + v),err-> System.out.println("err = " + err));
10:常用操作
filter、flatMap、concatMap、flatMapMany、transform、defaultIfEmpty、switchIfEmpty、concat、concatWith、merge、mergeWith、mergeSequential、zip、zipWith...
今日内容:
-
常用操作
-
错误处理
-
超时与重试
-
Sinks工具类
-
-
单播
-
多播
-
重放
-
背压
-
缓存
-
-
阻塞式API
-
-
block
-
-
Context-API:响应式中的ThreadLocal
-
-
ThreadLocal机制失效
Flux.just(1,2,3).transformDeferredContextual((flux,context)->{System.out.println("flux = " + flux);System.out.println("context = " + context);return flux.map(i->i+"==>"+context.get("prefix"));})//上游能拿到下游的最近一次数据.contextWrite(Context.of("prefix","哈哈"))//ThreadLocal共享了数据,上游的所有人能看到; Context由下游传播给上游.subscribe(v-> System.out.println("v = " + v));
-
-
ParallelFlux:
-
-
并发流
-
-
Flux.range(1,1000000).buffer(100).parallel(8).runOn(Schedulers.newParallel("yy")) .log() .subscribe();
相关文章:
Reactor详解
目录 1、快速上手 介绍 2、响应式编程 2.1. 阻塞是对资源的浪费 2.2. 异步可以解决问题吗? 2.3.1. 可编排性与可读性 2.3.2. 就像装配流水线 2.3.3. 操作符(Operators) 2.3.4. subscribe() 之前什么都不会发生 2.3.5. 背压 2.3.6. …...
实践航拍小目标检测,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建无人机航拍场景下的小目标检测识别分析系统
关于无人机相关的场景在我们之前的博文也有一些比较早期的实践,感兴趣的话可以自行移步阅读即可: 《deepLabV3Plus实现无人机航拍目标分割识别系统》 《基于目标检测的无人机航拍场景下小目标检测实践》 《助力环保河道水质监测,基于yolov…...
分布式数据库中全局自增序列的实现
自增序列广泛使用于数据库的开发和设计中,用于生产唯一主键、日志流水号等唯一ID的场景。传统数据库中使用Sequence和自增列的方式实现自增序列的功能,在分布式数据库中兼容Oracle和MySQL等传统数据库语法,也是基于Sequence和自增列的方式实现…...
【论文阅读】TensoRF: Tensorial Radiance Fields 张量辐射场
发表于ECCV2022. 论文地址:https://arxiv.org/abs/2203.09517 源码地址:https://github.com/apchenstu/TensoRF 项目地址:https://apchenstu.github.io/TensoRF/ 摘要 本文提出了TensoRF,一种建模和重建辐射场的新方法。不同于Ne…...
深入了解 Android 中的 FrameLayout 布局
FrameLayout 是 Android 中常用的布局之一,它允许子视图堆叠在一起,可以在不同位置放置子视图。在这篇博客中,我们将详细介绍 FrameLayout 的属性及其作用。 <FrameLayout xmlns:android"http://schemas.android.com/apk/res/androi…...
高级大数据技术 实验一 scala编程
高级大数据技术 实验一 scala编程 写的不是很好,大家多见谅! 1. 计算水仙花数 实验目标; (1) 掌握scala的数组,列表,映射的定义与使用 (2) 掌握scala的基本编程 实验说明 …...
使用Fabric创建的canvas画布背景图片,自适应画布宽高
之前的文章写过vue2使用fabric实现简单画图demo,完成批阅功能;但是功能不完善,对于很大的图片就只能显示一部分出来,不符合我们的需求。这就需要改进,对我们设置的背景图进行自适应。 有问题的canvas画布背景 修改后的…...
枚举与尺取法(蓝桥杯 c++ 模板 题目 代码 注解)
目录 组合型枚举(排列组合模板()): 排列型枚举(全排列)模板: 题目一(公平抽签 排列组合): 编辑 代码: 题目二(座次问题 全排…...
11、电源管理入门之Regulator驱动
目录 1. Regulator驱动是什么? 2. Regulator框架介绍 2.1 regulator consumer 2.2 regulator core 2.3 regulator driver 3. DTS配置文件及初始化 4. 运行时调用 5. Consumer API 5.1 Consumer Regulator Access (static & dynamic drivers) 5.2 Regulator Outp…...
24年证券从业考试注册报名流程详细图解,千万不要错过报名哦!
(一)时间安排 考生报名缴费时间:3月5日15时至3月8日15时 退费时间:3月7日15时至3月10日15时 准考证打印时间:3月20日15时至3月23日18时 (二)注册流程 1、进入中国证券业协会网站-从业人员管理-考…...
Git入门学习笔记
Git 是一个非常强大的分布式版本控制工具! 在下载好Git之后,鼠标右击就可以显示 Git Bash 和 Git GUI,Git Bash 就像是在电脑上安装了一个小型的 Linux 系统! 1. 打开 Git Bash 2. 设置用户信息(这是非常重要的&…...
⭐每天一道leetcode:27.移除元素(简单;vector)
⭐今日份题目 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中…...
如何处理Android内存泄漏和性能优化
处理Android内存泄漏和性能优化是一个复杂的过程,涉及到对应用的深入理解和良好的编程习惯。以下是一些关键的步骤和建议: 1. **理解内存泄漏的本质**: - 内存泄漏(Memory Leak)发生在程序中,当不再需要…...
应用方案 | D722 9MHz,轨对轨I/O CMOS运放,低噪声、低电压、低功耗运放,应用广泛
D722是低噪声、低电压、低功耗运放,应用广泛。D722具有9MHz的高增益带宽积,转换速率为8.5V/μs,静态电流为1.7mA(5V电源电压)。 D722具有低电压、低噪声的特点,并提供轨到轨输出能力,D722的最大…...
小程序常用样式和组件
常用样式和组件 1. 组件和样式介绍 在开 Web 网站的时候: 页面的结构由 HTML 进行编写,例如:经常会用到 div、p、 span、img、a 等标签 页面的样式由 CSS 进行编写,例如:经常会采用 .class 、#id 、element 等选择器…...
《Redis 设计与实现》读书概要
注: 《Redis 设计与实现》一书基于 Redis 2.9 版本编写,部分内容已过时,过时之处本文会有所说明。本文为读书笔记,部分简单和日常使用较少的知识点未记录。原书网页版地址 https://redisbook.com/ 一、底层数据结构 SDS(Simple Dy…...
Docker之数据卷自定义镜像
文章目录 前言一、数据卷二、自定义镜像 前言 Docker提供了一个持久化存储数据的机制,与容器生命周期分离,从而带来一系列好处: 总的来说Docker 数据卷提供了一种灵活、持久、可共享的存储机制,使得容器化应用在数据管理方面更加…...
Docker技术概论(4):Docker CLI 基本用法解析
Docker技术概论(4) Docker CLI 基本用法解析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:http…...
【JAVA重要知识 | 第五篇】暴打Java8新特性—(Lambda、方法引用、Stream流、函数式接口、Date Time API、Optional类)
文章目录 5.Java8新特性5.1新特性列表5.2Lambda 表达式5.2.1函数式思想5.2.2举例(1)方式一:创建对象(2)方式二:匿名内部类(3)方式三:Lambda 5.2.3Lambda表达式的标准格式…...
Docker Swarm全解析:实现微服务高可用与故障转移的秘密武器
🐇明明跟你说过:个人主页 🏅个人专栏:《Docker入门到精通》 《k8s入门到实战》🏅 🔖行路有良友,便是天堂🔖 目录 一、基本概念和介绍 1、Docker Swarm 是什么,它与 …...
03、数据结构与算法--单向链表
一种比顺序表稍微复杂些的结构... 一、认识链表 1、基本结构 链表是一个个结点构成的,就像火车 顺序表可以通过get方法(传入下标)来获取表,因为它们的地址是连续的 与顺序表不同的是,链表的物理存储不连续,要获取某个结点的话不…...
LangChain与向量库集成:Document Loaders与Text Splitters
上周三凌晨两点,我被一个奇怪的召回问题卡住了:明明在PDF里写得很清楚的配置项,用相似问题去查向量库,总是返回一些边缘内容。打开调试日志一看,发现切出来的文本片段里,前半段是某个章节的结尾,…...
XGO Rider:双轮足AI机器人如何通过ChatGPT重塑智能教育体验
1. 当双轮足机器人遇上ChatGPT:教育场景的颠覆者 第一次见到XGO Rider在桌面上灵活旋转时,我仿佛看到了科幻电影里的场景。这个身高不到16厘米的小家伙,却能像人类一样保持平衡,用两个轮子完成前进、后退甚至原地转圈的动作。但真…...
从机械臂到无人机:手把手教你用C++实现一个简易PID控制器(附完整代码)
从机械臂到无人机:手把手教你用C实现一个简易PID控制器(附完整代码) 在嵌入式开发和机器人控制领域,PID控制器就像一位不知疲倦的调音师,时刻调整着系统的"音准"。想象一下,当你操控无人机时&am…...
VSCode+PlatformIO环境下ESP32驱动1.3寸TFT屏幕:TFT_eSPI与lvgl配置实战
1. 硬件准备与接线指南 第一次接触ESP32和TFT屏幕时,最让我头疼的就是接线问题。我用的是一块1.3寸240240分辨率的SPI接口TFT屏幕,这种七针屏幕在淘宝上很常见,价格也很亲民。屏幕背面通常会标注引脚定义,如果没有的话可以找卖家要…...
3步掌握NormalMap-Online:免费在浏览器中生成专业法线贴图
3步掌握NormalMap-Online:免费在浏览器中生成专业法线贴图 【免费下载链接】NormalMap-Online NormalMap Generator Online 项目地址: https://gitcode.com/gh_mirrors/no/NormalMap-Online 还在为3D模型缺乏表面细节而烦恼吗?NormalMap-Online让…...
基于COMSOL的相变模拟:石蜡、熔盐、金属等的奇妙相变之旅
基于COMSOL的相变模拟(石蜡、熔盐、金属等) 材料从完全固态转变到液态(或者液态冷却到固态),考虑液相的自然对流对相变过程的影响 材料的参数设定与融化或凝固状态相关,如图中所示最近在研究材料的相变过程…...
Claude Code 源码泄露,拿来改造 OpenClaw
一场意外的源码泄露,意外地给开源AI助手社区带来了一份珍贵的“研究素材”。Claude Code近51万行源码的暴露,正好可以为OpenClaw的下一阶段发展,提供一个明确的架构升级蓝图。核心功能:自动化定时任务 (Cron)两者都将“时间管理”…...
Netty-socketio 开源贡献全流程:5步掌握Java实时通信框架开发
Netty-socketio 开源贡献全流程:5步掌握Java实时通信框架开发 【免费下载链接】netty-socketio Socket.IO server implemented on Java. Realtime java framework 项目地址: https://gitcode.com/gh_mirrors/ne/netty-socketio Netty-socketio 是一个基于Net…...
量子囚笼小说(理论分析)
1,困顿 最近,地球物理研究所的研究员李吕薇媛,心头始终萦绕着一团难解的烦恼。当下的世界,正浮现着种种诡异的失衡与怪象:有人坐拥无尽财富,生活极尽繁华优渥;有人却深陷困顿,日子举…...
