当前位置: 首页 > news >正文

精密数据工匠:探索 Netty ChannelHandler 的奥秘

通过上篇文章(Netty入门 — Channel,把握 Netty 通信的命门),我们知道 Channel 是传输数据的通道,但是有了数据,也有数据通道,没有数据加工也是没有意义的,所以今天学习 Netty 的第四个组件:ChannelHandler ,它是 Netty 的数据加工厂。

ChannelHandler

在上篇文章(Netty入门 — Channel,把握 Netty 通信的命门)中,大明哥提到:EventLoop 接收到 Channel 的 I/O 事件后会将该事件转交给 Handler 来处理,这个 Handler 就是 ChannelHandler。ChannelHandler 它是对 Netty 输入输出数据进行加工处理的载体,它包含了我们应用程序业务处理的逻辑

在整个 Netty 生产线上,它就是那个埋头苦干,一心干活的工人。

Channel 的状态处理

在介绍 Channel 的时候(Netty入门 — Channel,把握 Netty 通信的命门)我们知道,Channel 是有状态的,而且 Channel 也提供了判断 Channel 当前状态的 API,如下:

  • isOpen():检查 Channel 是否为 open 状态。
  • isRegistered():检查 Channel 是否为 registered 状态。
  • isActive():检查 Channel 是否为 active 状态。

上面三个 API 对应了 Channel 四个状态:

状态描述
ChannelUnregisteredChannel 已经被创建,但还未注册到 EventLoop。此时 isOpen() 返回 true,但 isRegistered() 返回 false。
ChannelRegisteredChannel 已经被注册到 EventLoop。此时 isRegistered() 返回 true,但 isActive() 返回 false。
ChannelActiveChannel 已经处理活动状态并可以接收与发送数据。此时 isActive() 返回 true。
ChannelInactiveChannel 没有连接到远程节点

状态变更如下:

当 Channel 的状态发生改变时,会生成相对应的事件,这些事件会被转发给 ChannelHandler,而 ChannelHandler 中会有相对应的方法来对其进行响应。在 ChannelHandler 中定义一些与这生命周期相关的 API,如 channelRegistered()channelUnregistered()channelActive()channelInactive()等等,后面大明哥会详细介绍这些 API。

ChannelHandler 的家族

ChannelHandler 的大家族如下图:

ChannelHandler 是整个家族中的顶层接口,它有两大子接口,ChannelInBoundHandler 和 ChannelOutBoundHandler,其中 ChannelInBoundHandler 用于处理入站数据(读请求),ChannelOutBoundHandler 用于处理出站数据(写请求),他们两个接口都有一个相对应的默认实现,即 XxxxAdapter。

ChannelHandler

ChannelHandler 作为顶层接口,它并不具备太多功能,它仅仅只提供了三个 API:

API描述
handlerAdded()当ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved()当 ChannelHandler 被从 ChannelPipeline 移除时调用
exceptionCaught()当 ChannelHandler 在处理过程中出现异常时调用

从 ChannelHandler 提供的 API 中我们可以看出,它并不直接参与 Channel 的数据加工过程,而是用来响应 ChannelPipeline 链和异常处 理的,对于 Channel 的数据加工则由它的子接口处理:

  • ChannelInboundHandler:拦截&处理各种入站的 I/O 事件
  • ChannelOutboundHandler:拦截&处理各种出站的 I/O 事件

这里解释下出站和入站的概念:

  • 接收对方传输的数据并处理,即为入站
  • 向对方写数据时,即为出站
  • 如:客户端 —> 服务端:服务端读取客户端消息为入站,服务端处理消息完后给客户端返回消息为出站

ChannelInboundHandler

ChannelInboundHandler 用来处理和拦截各种入站的 I/O 事件,它可以处理的事件如下:

响应方法触发事件
channelRegisteredChannel 被注册到EventLoop 时
channelUnregisteredChannel 从 EventLoop 中取消时
channelActiveChannel 处理活跃状态,可以读写时
channelInactiveChannel 不再是活动状态且不再连接它的远程节点时
channelReadCompleteChannel 从上一个读操作完成时
channelReadChannel 读取数据时
channelWritabilityChangedChannel 的可写状态发生改变时
userEventTriggeredChannelInboundHandler.fireUserEventTriggered()方法被调用时

一般情况我们不直接使用 ChannelInboundHandler,而是使用它的实现类 ChannelInboundHandlerAdapter。我们写业务处理时直接继承 ChannelInboundHandlerAdapter ,然后重写你感兴趣的 I/O 事件响应方法即可,比如你想处理 Channel 读数据,那重写 channelRead() 即可,代码如下:

public class ChannelHandlerTest_1 extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// do something}
}

但是在使用 ChannelInboundHandlerAdapter 的时候,需要注意的是我们需要显示地释放与池化 ByteBuf 实例相关的内存,Netty 为此专门提供了一个方法 ReferenceCountUtil.release(),即我们需要在 ChannelInboundHandler 的链的末尾需要使用该方法来释放内存,如下:

public class ByteBufReleaseHandler extends ChannelInboundHandlerAdapter{@Overridepublic void channelRead(ChannelHandlerContext ctx,Object msg){//释放msgReferenceCountUtil.release(msg);}
}

但是有些小伙伴有时候会忘记这点,会带来不必要的麻烦,那有没有更好的方法呢?Netty 提供了一个类来帮助我们简化这个过程: SimpleChannelInboundHandler,对于我们业务处理的类,采用继承 SimpleChannelInboundHandler 而不是 ChannelInboundHandlerAdapter 就可以解决了。

public class ChannelHandlerTest_1 extends SimpleChannelInboundHandler<Object> {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// do something}
}

使用 SimpleChannelInboundHandler 我们就不需要显示释放资源了,是不是非常人性化。

ChannelOutboundHandler

ChannelOutboundHandler 是拦截和处理出站的各种 I/O 事件的。处理的事件如下:

响应方法触发事件
bind请求将 Channel 绑定到本地地址时
connect请求将 Channel 连接到远程节点时
disconnect请求将 Channel 从远程节点断开时
close请求关闭 Channel 时
dderegister请求将 Channel 从它的 EventLoop 注销时
read请求从 Channel 中读取数据时
flush请求通过 Channel 将入队数据刷入远程节点时
write请求通过 Channel 将数据写入远程节点时

与 ChannelInboundHandler 一样,我们也不直接使用 ChannelOutboundHandler 接口,而是使用它的默认实现类 ChannelOutboundHandlerAdapter,重写我们想处理的 I/O 事件的响应方法就可以了。

ChannelHandler 的示例

ChannelHandler 生命周期

大明哥通过一个示例来告诉你 ChannelHandler 在整个执行过程中的生命周期是怎么样的,响应方法调用的顺序是如何的。我们只有了解了整个生命周期的运行,才能在合适的生命周期响应方法里面扩展我们自己的业务功能。

  • 服务端代码
public class ChannelHandlerTest_1_server extends ChannelInboundHandlerAdapter {public static void main(String[] args) {new ServerBootstrap().group(new NioEventLoopGroup()).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new ChannelHandlerTest());}}).bind(8081);}private static class ChannelHandlerTest extends ChannelInboundHandlerAdapter {@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--handlerAdded:handler 被添加到 ChannelPipeline");super.handlerAdded(ctx);}@Overridepublic void channelRegistered(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--channelRegistered:Channel 注册到 EventLoop");super.channelRegistered(ctx);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--channelActive:Channel 准备就绪");super.channelActive(ctx);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println(LocalTime.now().toString() + "--channelRead:Channel 中有可读数据");super.channelRead(ctx, msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--channelReadComplete:Channel 读取数据完成");super.channelReadComplete(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--channelInactive:Channel 被关闭,不在活跃");super.channelInactive(ctx);}@Overridepublic void channelUnregistered(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--channelUnregistered:Channe 从 EventLoop 中被取消");super.channelUnregistered(ctx);}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {System.out.println(LocalTime.now().toString() + "--handlerRemoved:handler 从 ChannelPipeline 中移除");super.handlerRemoved(ctx);}}
}

服务端重写整个生命周期中的各个方法。

  • 客户端
public class ChannelHandlerTest_1_client extends ChannelInboundHandlerAdapter {public static void main(String[] args) throws Exception {Channel channel = new Bootstrap().group(new NioEventLoopGroup()).channel(NioSocketChannel.class).handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());}}).connect("127.0.0.1",8081).sync().channel();for (int i = 1 ; i <= 2 ; i++) {channel.writeAndFlush("hello,i am ChannelHander client -" + i);TimeUnit.SECONDS.sleep(2);}channel.close();}
}

客户端连接服务端后,每个 2 秒向服务端发送一次消息,共发两次。发送完消息后,再过 2 秒关闭连接。

  • 运行结果
11:45:09.456--handlerAdded:handler 被添加到 ChannelPipeline
11:45:09.457--channelRegistered:Channel 注册到 EventLoop
11:45:09.457--channelActive:Channel 准备就绪11:45:09.474--channelRead:Channel 中有可读数据
11:45:09.475--channelReadComplete:Channel 读取数据完成11:45:11.397--channelRead:Channel 中有可读数据
11:45:11.397--channelReadComplete:Channel 读取数据完成11:45:13.405--channelReadComplete:Channel 读取数据完成
11:45:13.407--channelInactive:Channel 被关闭,不在活跃
11:45:13.407--channelUnregistered:Channe 从 EventLoop 中被取消
11:45:13.407--handlerRemoved:handler 从 ChannelPipeline 中移除

结果分析如下:

  • 服务端检测到客户端发起连接后,会将要处理的 Handler 添加到 ChannelPipeline 中,然后将 Channel 注册到 EventLoop,注册完成后,Channel 准备就绪处于活跃状态,可以接收消息了
  • 客户端向服务端发送消息,服务端读取消息
  • 当服务端检测到客户端已关闭连接后,该 Channel 就被关闭了,不再活跃,然后将该 Channel 从 EventLoop 取消,并将 Handler 从 ChannelPipeline 中移除。

在整个生命周期中,响应方法执行顺序如下:

  1. 建立连接handlerAdded() -> channelRegistered() -> channelActive ()
  2. 数据请求channelRead() -> channelReadComplete()
  3. 关闭连接channelReadComplete() -> channelInactive() -> channelUnregistered() -> handlerRemoved()

这里有一点需要注意,为什么关闭连接会响应 channelReadComplete() 呢?这里埋个点,后续大明哥在来说明,有兴趣的小伙伴可以先研究研究。

这里大明哥对 ChannelHandler 生命周期的方法做一个总结:

  1. handlerAdded():ChannelHandler 被加入到 Pipeline 时触发。当服务端检测到新链接后,会将 ChannelHandler 构建成一个双向链表(下篇文章介绍),该方法被触发表示在当前 Channel 中已经添加了一个 ChannelHandler 业务处理链了》。
  2. channelRegistered():当 Channel 注册到 EventLoop 中时被触发。该方法被触发了,表明当前 Channel 已经绑定到了某一个 EventLoop 中了。
  3. channelActive():Channel 连接就绪时触发。该方法被触发,说明当前 Channel 已经处于活跃状态了,可以进行数据读写了。
  4. channelRead():当 Channel 有数据可读时触发。客户端向服务端发送数据,都会触发该方法,该方法被调用说明有数据可读。而且我们自定义业务 handler 时都是重写该方法。
  5. channelReadComplete():当 Channel 数据读完时触发。服务端每次读完数据后都会触发该方法,表明数据已读取完毕。
  6. channelInactive():当 Channel 断开连接时触发。该方法被触发,说明 Channel 已经不再是活跃状态了,连接已经关闭了。
  7. channelUnregistered():当 Channel 取消注册时触发:连接关闭后,我们就要取消该 Channel 与 EventLoop 的绑定关系了。
  8. handlerRemoved():当 ChannelHandler 被从 ChannelPipeline 中移除时触发。将与该 Channel 绑定的 ChannelPipeline 中的 ChannelHandler 业务处理链全部移除。

ChannelHandler 业务开发

上面只是一个很简单的 ChannelHandler 实例,但是我们在实际项目开发中,要处理的业务复制多了,它肯定是由多个业务组合而成,那我们又该如何去做呢?

一个 ChannelHandler

伪代码如下:

public class ChannelHandlerTest extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Map<String,String> map=(Map<String,String>)msg;String code=map.get("code");if(code.equals("xxx1")){//do something 1}else if(code.equals("xxx2")){//do something 2}else{// do something 3}}
}

这种实现方式简单,非常容易实现,但是如果业务较多的情况下,该 Handler 的处理逻辑会非常臃肿,而且很不好维护,而且这么多 if…else ,看起来就烦,当然我们可以采用相对应的方式来干掉 if…else ,例如利用策略模式:

public class ChannelHandlerTest extends ChannelInboundHandlerAdapter {HashMap<String,HandlerService) handlerServiceMap = new HashMap();@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Map<String,String> map=(Map<String,String>)msg;String code=map.get("code");handlerServiceMap.get(code).doSomething(map);}
}

这种方式也行,但他把所有的业务都耦合在一起了,终究不是那么优雅。

多个 ChannelHandler

我们可以定义多个 ChannelHandler,根据 code 来判断,如果 code 是自己的则处理,否则流转到下一个节点:

public class ChannelHandlerTest extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Map<String,String> map=(Map<String,String>)msg;String code=map.get("code");if(code.equals("xxx")){//自己的业务,处理}else{//不是自己的,流转到下一个节点super.channelRead(ctx, msg);}}
}

这种方式将所有业务解耦了,每个 Handler 各司其职,所有业务不再是都耦合在一起了,后期维护更加轻松。这种方式和上面方式的一样,依赖 code,他们都需要服务端和客户端都维护一套 code,而这个 code 如果还不能轻易发生变更。

自定义类型

根据客户端提交的参数类型,自动流转到相对应的 ChannelHandler。

public class ChannelHandlerTest extends SimpleChannelInboundHandler<User> {protected void channelRead0(ChannelHandlerContext channelHandlerContext, User user) throws Exception {// do something}
}

这种方式是最优雅的,也是我们使用最多的方式。他的优点明显,1. 业务解耦,2. 不需要维护码表。

总结

  1. ChannelHandler 是 Netty 中真正做事的组件,EventLoop 将监听到的 I/O 事件转发后,就由 ChannelHandler 来处理,它也是我们编写 Netty 代码最多的地方。
  2. ChannelHandler 作为顶层接口,它一般不会负责具体业务 I/O 事件,具体的业务 I/O 事件由它两个子接口负责:
    1. ChannelInboundHandler:负责拦截响应入站 I/O 事件
    2. ChannelOutboundHandler:负责拦截响应出站 I/O 事件
  3. 我们自定义的业务 Handler ,一般不会直接实现 ChannelInboundHandler 和 ChannelOutboundHandler,而是继承他们两个的默认实现类:ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter,这样我们就可以只需要关注我们想关注的 I/O 事件即可。
  4. 在这节内容中,最重要的是理解 ChannelHandler 的生命周期方法,在文章总已多次总结了,这里不再阐述了。

虽然 ChannelHandler 是一个好的打工人,但是它没办法一个人干所有的活啊,他需要有其他的 ChannelHandler 来配合它,这个时候怎么办?大明哥下篇文章揭晓!

示例源码:http://suo.nz/1v8w02

相关文章:

精密数据工匠:探索 Netty ChannelHandler 的奥秘

通过上篇文章&#xff08;Netty入门 — Channel&#xff0c;把握 Netty 通信的命门&#xff09;&#xff0c;我们知道 Channel 是传输数据的通道&#xff0c;但是有了数据&#xff0c;也有数据通道&#xff0c;没有数据加工也是没有意义的&#xff0c;所以今天学习 Netty 的第四…...

Python四种基本结构的操作

列表 列表的创建有两种方法 SampleList [] SampleList list() 列表中元素的添加 append(obj)&#xff1a;在列表末尾添加元素obj extend(seq)&#xff1a;在列表末尾添加多个值&#xff0c;使用extend()函数&#xff0c;seq是一个可迭代对象&#xff0c;否则报错。 Inser…...

Eureka:com.netflix.discovery.TimedSupervisorTask - task supervisor timed out

1、原因是spring cloud netflix中&#xff0c;某个服务挂掉了或者是执行某个任务时间过长&#xff0c;而没有发送给Eureka心跳 &#xff0c;导致调用不到指定的服务&#xff0c;所以检查被调用服务器是否有问题。 2、有可能是某一个微服务自身内部G了&#xff0c;导致没有向eu…...

1.spark standalone环境安装

概述 环境是spark 3.2.4 hadoop版本 3.2.4&#xff0c;所以官网下载的包为 spark-3.2.4-bin-hadoop3.2.tgz 在具体安装部署之前&#xff0c;需要先下载Spark的安装包&#xff0c;进到 spark的官网&#xff0c;点击download按钮 使用Spark的时候一般都是需要和Hadoop交互的&a…...

【问题解决】 avue dicUrl 动态参数加载字典数据(已解决)

事情是这样的&#xff0c;用了avue-crud组件&#xff0c;配置了一个option。     现在有一列source属性要展示为 多选的下拉框 &#xff0c;当然问题不在这而在于&#xff0c;选项是需要根据同级别属性id去拿的。也就是option.column.source 的配置中 需要该行的option.col…...

​学习一下,什么是预包装食品?​

预包装食品&#xff0c;指预先定量包装或者制作在包装材料和容器中的食品&#xff1b;包括预先定量包装以及预先定量制作在包装材质和容器中并且在一定量限范围内具有统一的质量或体积标识的食品。简单说&#xff0c; 就是指在包装完成后即具有确定的量值&#xff0c;这一确定的…...

从零开始学习搭建量化平台笔记

从零开始学习搭建量化平台笔记 本笔记由纯新手小白开发学习记录&#xff0c;欢迎大佬请教指点留言&#xff0c;有空的话还可以认识一下&#xff0c;来上海请您喝咖啡~~ 2023/10/30&#xff1a;上份工作辞职并休息了几个月后&#xff0c;打算开始找个关于量化投资相关的工作。面…...

【whisper】在python中调用whisper提取字幕或翻译字幕到文本

最近在做视频处理相关的业务。其中有需要将视频提取字幕的需求&#xff0c;在我们实现过程中分为两步&#xff1a;先将音频分离&#xff0c;然后就用到了whisper来进行语音识别或者翻译。本文将详细介绍一下whisper的基本使用以及在python中调用whisper的两种方式。 一、whispe…...

git diff对比差异时指定或排除特定的文件和目录

文章目录 前言git diff指定或者排除文件指定文件和目录排除文件和目录 番外篇总结 前言 你一般什么时候会用GPT? 居然会有这种话题&#xff0c;答案就是作为程序员的我天天在用&#xff0c;虽然GPT有个胡说八道的毛病&#xff0c;但试试总没错的&#xff0c;就比如今天题目中这…...

数据结构介绍与时间、空间复杂度

数据结构介绍 什么是数据结构&#xff1f;什么是算法&#xff1f;数据结构和算法的重要性 数据结构定义 数据结构是计算机科学中研究数据组织、存储和管理的一门学科。数据结构描述了数据对象之间的关系&#xff0c;以及对数据对象进行操作的方法和规则。 常见的数据结构 数…...

(c语言进阶)字符串函数、字符分类函数和字符转换函数

一.求字符串长度 1.strlen() (1)基本概念 头文件&#xff1a;<string.h> (2)易错点&#xff1a;strlen()的返回值为无符号整形 #include<stdio.h> #include<string.h> int main() {const char* str1 "abcdef";const char* str2 "bbb&q…...

解决MySQL大版本升级导致.Net(C#)程序连接报错问题

数据库版本从MySQL 5.7.21 升级到 MySQL8.0.21 数据升级完成后&#xff0c;直接修改程序的数据库连接配置信息 <connectionStrings> <add name"myConnectionString" connectionString"server192.168.31.200;uidapp;pwdFgTDkn0q!75;databasemail;&q…...

Java 将对象List转为csv文件并上传远程文件服务器实现方案

问题情景&#xff1a; 最近项目中遇到了根据第三方系统传递过来的参数&#xff0c;封装为List<实体类对象>后&#xff0c;将该实体类转换为csv文件&#xff0c;然后上传到远程的sftp服务器指定目录的需求。 实现思路&#xff1a; List<实体类对象>转为csv文件的…...

分享8个分布式Kafka的使用场景

Kafka 最初是为海量日志处理而构建的。它保留消息直到过期&#xff0c;并让消费者按照自己的节奏提取消息。与它的前辈不同&#xff0c;Kafka 不仅仅是一个消息队列&#xff0c;它还是一个适用于各种情况的开源事件流平台。 1. 日志处理与分析 下图显示了典型的 ELK&#xff0…...

【再见了暗恋对象 朋友们看完之后的一些感悟】

【再见了暗恋对象】写完之后魏野是我的第一个读者&#xff0c;魏野的反应是&#xff1a;这就是青春啊&#xff0c;喜欢了一个不喜欢自己的人而且男生觉得很困扰女孩子喜欢被牵引着走&#xff0c;但是男孩子牵引就是因为不喜欢这个女孩子&#xff0c;好可怜&#xff01;青春就这…...

JSON和Protobuf序列化

文章目录 一、粘包和拆包1、半包问题2、半包现象原理 二、JSON协议通信1、通用类库2、JSON传输的编码器和解码器 三、Protobuf协议通信1、一个简单的proto文件的实践案例2、生成POJO和Builder3、消息POJO和Builder的使用案例1&#xff09;构造POJO消息对象2&#xff09;序列化和…...

lambda表达式 - c++11

文章目录&#xff1a; lambda表达式概念lambda表达式语法函数对象与lambda表达式 lambda表达式概念 lambda 表达式是 c11 中引入的一种匿名函数&#xff0c;它可以在需要函数对象的地方使用&#xff0c;可以用作函数参数或返回值。lambda 表达式可以看作是一种局部定义的函数对…...

509. 斐波那契数

斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n > 1给定 n &a…...

四、[mysql]索引优化-1

目录 前言一、场景举例1.联合索引第一个字段用范围查询不走索引(分情况&#xff09;2.强制走指定索引3.覆盖索引优化4.in和or在表数据量比较大的情况会走索引&#xff0c;在表记录不多的情况下会选择全表扫描5.like 后% 一般情况都会走索引(索引下推) 二、Mysql如何选择合适的索…...

PyTorch入门学习(九):神经网络-最大池化使用

目录 一、数据准备 二、创建神经网络模型 三、可视化最大池化效果 一、数据准备 首先&#xff0c;需要准备一个数据集来演示最大池化层的应用。在本例中&#xff0c;使用了CIFAR-10数据集&#xff0c;这是一个包含10个不同类别图像的数据集&#xff0c;用于分类任务。我们使…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险

C#入门系列【类的基本概念】&#xff1a;开启编程世界的奇妙冒险 嘿&#xff0c;各位编程小白探险家&#xff01;欢迎来到 C# 的奇幻大陆&#xff01;今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类&#xff01;别害怕&#xff0c;跟着我&#xff0c;保准让你轻松搞…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...