Netty 是如何利用EventLoop实现千万级并发的

news2024/11/24 16:06:11

经过前面几篇文章的介绍,我们掌握了 Netty 的 5 个核心组件,但是有了这 5 个核心组件 Netty 这个工厂还是无法很好的运转,因为缺少了一个最核心的组件:EventLoop,它 是 Netty 中最最核心的组件,也是 Netty 最精华的部分,它负责 Netty 中 I/O 事件的分发,也就是说,这个事件谁来做,它说了算。

再谈 Reactor 线程模型

要想弄明白 Netty 的 EventLoop,我们就必须先理解 Reactor 线程模型,Netty 高性能的奥秘就在于 Reactor 线程模型。

线程模型的优劣直接决定了系统的性能,一个好的线程模型比不好的线程模型性能会高出好多倍。而目前主流的网络框架选择的线程模型几乎都是 I/O 多路复用的方案,Netty 作为其中的佼佼者更是演绎地淋漓尽致。

Java 并发包(java.util.concurrent)大神 Doug Lea 在Scalable I/O in Java 一文中通过各个角度,循序渐进地阐述了服务端开发中 I/O 模型的演变过程,文中基于 Reactor 反应器模式的几种模型,被 Netty 、Mina 等大多数高性能 I/O 服务框架所采用的,下面就对这几种 Reactor 模型做一个简要说明。

Scalable IO in Java:http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

单线程模型

  • 原版

  • 理解版

单线程模式是所有的 I/O 操作(建立连接、事件分发、数据读写等等)都是由一个线程完成,它的优点是模型够简单,实现起来容易,但是缺点也很明显:

  1. 单线程处理业务有限,无法充分利用多 CPU 的性能,性能方面存在瓶颈。
  2. 当多个事件同时触发,如果其中有某一个事件处理时间较长,则会导致其他事件无法被处理,容易造成消息积压。如果线程意外终止或者进入死循环了,将会导致整个系统不可用。

多线程模型

既然单线程模型存在性能瓶颈和可靠性问题,那我们就使用多线程方案。Reactor 多线程模型是将业务逻辑处理交个多个线程处理。

  • 原模型图

  • 理解模型图

多线程模型相比单线程模型,它可以充分利用多 CPU 的能力,性能方面有了较大的提升,同时也不会因为单一的业务线程处理较慢而影响整个系统,但是它依然是单线程去处理所有的事件监听及响应,在高并发下还是存在性能问题。

主从多线程模型

由于多线程模型对事件的监听和响应依然采用单线程方式,在高并发条件下还是存在性能瓶颈,所以衍生出了主从多线程模型,主从多线程模型有多个 Reactor 线程组成,每个 Reactor 有自己单独的 Selector 对象。MainReactor 负责客户端的 Accept 事件,建立连接后将其注册到 SubReactor 中,SubReactor 则负责具体的 I/O 事件。

  • 原型图:

  • 理解版

主从多线程模型足以应对高并发,轻轻松松达到成千上万规模的客户端连接。Netty 推荐采用主从多线程模型。需要明白的是一个 MainReactor 是可以对应多个 SubReactor ,在海量客户端并发请求的情况下,我们可以适当增加 SubReactor 线程的数量来提供系统的吞吐量。

对于 Reactor 模式,大明哥在【死磕 NIO】— Reactor 模式就一定意味着高性能吗?有详细的讲解,对其不是很理解的同学可以先去看看这篇文章。

EventLoop

为什么说 EventLoop 是 Netty 的精髓呢?因为 EventLoop 是 Netty Reactor 线程模型的核心处理引擎。要想明白 Netty 为什么可以轻松处理成千上万规模的客户端连接,就必须要掌握 EventLoop。由于本篇是入门篇,所以我们对其不会讲解很深入,掌握它的原理即可,为后面的进阶和源码分析打下基础。

EventLoop 从字面上来看就是 event + loop,也就是事件循环,事件就是 I/O 事件,循环则可以理解为 while(true)。直白点就是EventLoop 是通过循环方式来处理 I/O 事件的执行器。它的简要 UML 类图如下:

从上面我们可以看出 EventLoop 本质就是一个单线程执行器,这个执行器干嘛呢?就是处理 Channel 上的 IO 事件。

当客户端与服务端建立连接后,服务端会创建一个 Channel,并将该 Channel 与 EventLoop 绑定。绑定后,该 Channel 在整个生命周期内所有发生的事件都由该 EventLoop 来处理,但是一个 EventLoop 可以绑定多个 Channel。对于 EventLoop 而言,它就是通过不断循环它所绑定的 Channel 事件列表,检测是否有事件发生,如果有,则将该事件分发给 worker 线程处理。运行模式如下图:

EventLoopGroup

EventLoopGroup 就是一组 EventLoop ,它主要是来维护和管理 EventLoop,一般我们是不会直接使用 EventLoop 的,而是通过 EventLoopGroup 来使用它。我们看 EventLoopGroup 的源码就知道 EventLoopGroup 就提供了两个方法:

  • register() :当服务端创建一个 Channel 后,会调用 EventLoopGroup 的 register() 将 Channel 绑定到其中一个 EventLoop 上。
  • next():返回 EventLoopGroup 中维护的 EventLoop。

所以 Channel、EventLoop、EventLoopGroup 之间的关系如下:

  • 一个 EventLoopGroup 包含一个或者多个 EventLoop。
  • 一个 Channel 在它的声明周期中只会与一个 EventLoop 绑定。
  • 一个 EventLoop 可以绑定多个 Channel。
  • 一个 EventLoop 只会与一个 Thread 绑定,同时该 EventLoop 的所有 IO 事件都由这一个 Thread 执行。

如下图:

到这里我相信小伙伴们基本上知道了 EventLoop、EventLoopGroup 是干啥的了,他们的基本原理也差不多了解了,在上面 Reactor 线程模型中,大明哥提到,Netty 推荐我们采用主从多线程模型,这了大明哥就主从多线程模型把 Channel、EventLoop、EventLoopGroup 再阐述一遍,便于小伙伴们理解更深。先看图:

  • BoosEventLoopGroup 负责监听客户端的 Accept 事件,当事件触发时,会新建一个 Channel,同时将 Channel 注册到 WorkEventLoopGroup 中的某个 EventLoop 上。
  • WorkEventLoopGroup 中的 EventLoop 负责监听 Channel 的 read/write 事件,当有事件发生时,EventLoop 读取数据将交给与之绑定的 ChannelPipeline 的 ChannelHandler 处理。
  • 这里有两个一一对应的关系
    • 一个 Channel 对应一个 EventLoop,且整个生命周期内所有 IO 事件都是该 EventLoop 处理。
    • 一个 EventLoop 对应一个 Thread,该 EventLoop 中的所有 IO 事件都由该 Thread 处理。
    • 由于这两个一一对应的关系,Channel 生命周期的所有 IO 事件都是线程独立的。

我们在使用 EventLoop 的时候一般不会直接使用它,而是通过 EventLoopGroup 来使用,我们常用的 EventLoopGroup 有两种:

  • NioEventLoopGroup:它处理IO事件、普通任务、定时任务
  • DefaultEventLoopGroup:处理普通任务,定时任务

示例

上面大明哥详细阐述了 EventLoop、EventLoopGroup 的核心原理,下面我们来使用他们,进一步掌握他们。

获取 EventLoop

EventLoopGroup 提供了 next() 方法,用于我们获取它管理的 EventLoop。

public class EventLoopTest_1 {
    public static void main(String[] args) {
        // 创建两个线程
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);

        log.info("{}",eventLoopGroup.next());
        log.info("{}",eventLoopGroup.next());
        log.info("{}",eventLoopGroup.next());
        log.info("{}",eventLoopGroup.next());
    }
}

执行结果

...EventLoopTest_1 - io.netty.channel.nio.NioEventLoop@6ea6d14e
...EventLoopTest_1 - io.netty.channel.nio.NioEventLoop@6ad5c04e
...EventLoopTest_1 - io.netty.channel.nio.NioEventLoop@6ea6d14e
...EventLoopTest_1 - io.netty.channel.nio.NioEventLoop@6ad5c04e

从执行结果可以看出,next() 按照顺序的方式返回 NioEventLoop,其实next() 是通过一个算法来选择返回哪一个 NioEventLoop 的。

new NioEventLoopGroup(2) 指定创建的 NioEventLoop 线程数,如果我们不指定则默认为 cpu * 2。

执行普通任务

EventLoop 继承 Executor,可定可以执行任务拉。它提供了 submit()execute() 两个方法来执行任务。

public class EventLoopTest_2 {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);

        eventLoopGroup.next().submit(() -> {
            log.info("hello,我是大明哥,这是 死磕 Netty 系列-111");
        });

        eventLoopGroup.next().submit(() -> {
            log.info("hello,我是大明哥,这是 死磕 Netty 系列-222");
        });

        log.info("这是主线程拉..");
    }
 }

执行结果

22:50:16.221 [main] INFO ...EventLoopTest_2 - 这是主线程拉..
22:50:16.222 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_2 - hello,我是大明哥,这是 死磕 Netty 系列-111
22:50:16.222 [nioEventLoopGroup-2-2] INFO ...EventLoopTest_2 - hello,我是大明哥,这是 死磕 Netty 系列-222

执行定时任务

EventLoop 也继承 ScheduledExecutorService ,所以它也具备执行定时任务的功能,它提供了四个 scheduleXxx() 方法来执行定时任务。

public class EventLoopTest_3 {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);

        eventLoopGroup.next().scheduleAtFixedRate(() ->{
            log.info("hello,我是大明哥,这是 死磕 Netty 系列");
        },1,2, TimeUnit.SECONDS);

        log.info("这是主线程拉..");
    }
}

执行结果

23:09:07.615 [main] INFO ...EventLoopTest_3 - 这是主线程拉..
23:09:08.616 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:10.616 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:12.616 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:14.615 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:16.615 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:18.615 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列
23:09:20.614 [nioEventLoopGroup-2-1] INFO ...EventLoopTest_3 - hello,我是大明哥,这是 死磕 Netty 系列

从执行结果中可以看出,延后 1 秒后,每隔 2 秒就执行一次任务。

处理 IO 事件

处理 IO 事件才是 EventLoop 的本职工作。

// 服务端
public class EventLoopTest_4_server {
    public static void main(String[] args) {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf byteBuf = (ByteBuf) msg;
                                log.info(byteBuf.toString(Charset.defaultCharset()));
                            }
                        });
                    }
                })
                .bind(8081);
    }
}

// 客户端
public class EventLoopTest_4_client {
    public static void main(String[] args) throws Exception {
        Channel channel = new Bootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringEncoder());
                    }
                })
                .connect("127.0.0.1",8081)
                .sync()
                .channel();

        for (int i = 1 ; i <= 5 ; i++) {
            channel.writeAndFlush("hello,我是 sike-java - " + i);
            TimeUnit.SECONDS.sleep(2);
        }
    }
}

服务端接收客户端信息打印出来,客户端每个 2 秒向服务端发送一次消息。

执行结果如下:

23:35:59.412 [nioEventLoopGroup-2-2] ...EventLoopTest_4_server - hello,我是 sike-java - 1
23:36:01.349 [nioEventLoopGroup-2-2] ...EventLoopTest_4_server - hello,我是 sike-java - 2
23:36:03.354 [nioEventLoopGroup-2-2] ...EventLoopTest_4_server - hello,我是 sike-java - 3
23:36:05.357 [nioEventLoopGroup-2-2] ...EventLoopTest_4_server - hello,我是 sike-java - 4
23:36:07.357 [nioEventLoopGroup-2-2] ...EventLoopTest_4_server - hello,我是 sike-java - 5

这里看到了服务端打印日志的线程一直都是 nioEventLoopGroup-2-2 ,这就充分表明了 Channel 是绑定在 EventLoop 的,它所有的 IO 时间都由该 EventLoop 处理。不信的话我们修改下客户端发送的消息:“hello,我是 sike-netty - i”,再来观察服务端打印的日志:

观察下蓝色部分和红色部分,是不是线程名不一样?那一个 EventLoop 绑定多个 Channel 呢?这个任务就交给小伙伴去验证吧。

EventLoop 线程细分

Netty 推荐我们采用主从多线程模型,但是从上面的示例其实使用的一直都是一个 EventLoopGroup,也就是多线程模式,其实切换成主从多线程的模式也是非常简单的。Netty 提供了 group(EventLoopGroup parentGroup, EventLoopGroup childGroup) 方法,我们利用这个方法可以将 BossEventLoopGroup 和 WorkerEventLoopGroup 进行分开,其中** BossEventLoopGroup 专门负责 ServerSocketChannel 上的 Accept 事件,WorkerEventLoopGroup 则负责 SocketChannel 上的读写**。

这种分工比较简单,大明哥就不演示了,我们来说说另外一种分工,我们知道 EventLoop 是需要绑定很多 Channel 的,且它是单线程处理的,假如它处理任务当中某个部分耗时非常长,则势必会影响到其他 Channel,降低整个系统的吞吐量,那怎么办呢?有两种方案:

  1. 将这部分逻辑抽离出来,采用异步方式,用其他线程来处理这部分逻辑
  2. 与上面方式一样,同样是将这部分逻辑抽离处理,只不过我们采用不同的 EventLoop 来执行这部分逻辑

这里大明哥只演示第 2 中方案。ChannelPipeline 提供了一个方法 addLast(EventExecutorGroup group, String name, ChannelHandler handler); 该方法接受三个参数:

  1. group:指定执行的 group
  2. name:name
  3. handler:处理 handler

我们可以利用该方法来实现方案 2 ,达到线程细分。如下:

public class EventLoopTest_5_server {
    public static void main(String[] args) {
        // 处理耗时较长的 EventLoopGroup
        EventLoopGroup otherGroup = new DefaultEventLoopGroup(2);

        new ServerBootstrap()
                .group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                ByteBuf byteBuf = (ByteBuf) msg;
                                String objectMsg = byteBuf.toString(Charset.defaultCharset());
                                log.info(objectMsg);

                                ctx.fireChannelRead(objectMsg);
                            }
                        });
                        ch.pipeline().addLast(otherGroup,"other-eventGroup",new ChannelInboundHandlerAdapter(){
                            @Override
                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                TimeUnit.SECONDS.sleep(10);
                                log.info("{}",msg);
                            }
                        });
                    }
                })
                .bind(8081);
    }
}

服务到端,在处理第二步的时候,使用了一个 otherGroup,在 handler 内部我们等待 10 秒。然后我们需要 3 个客户端:

  • 客户端1:发送“ hello,我是 sike-netty…”
  • 客户端2:发送“ hello,我是 sike-java…”
  • 客户端3:发送“ hello,我是 sike-redis…”

执行结果如下:

10:09:43.761 [nioEventLoopGroup-4-1] ...EventLoopTest_5_server - hello,我是 sike-netty...
10:09:49.889 [nioEventLoopGroup-4-2] ...EventLoopTest_5_server - hello,我是 sike-java...
10:09:53.763 [defaultEventLoopGroup-2-1] ...EventLoopTest_5_server - hello,我是 sike-netty...
10:09:56.974 [nioEventLoopGroup-4-1] ...EventLoopTest_5_server - hello,我是 sike-redis...
10:09:59.890 [defaultEventLoopGroup-2-2] ...EventLoopTest_5_server - hello,我是 sike-java...
10:10:06.977 [defaultEventLoopGroup-2-1] ...EventLoopTest_5_server - hello,我是 sike-redis...

从执行结果中我们可以看出,同一个客户端的消息是由两个不同的 EventLoop 来执行的,一个是 nioEventLoopGroup,一个是 defaultEventLoopGroup,同时 defaultEventLoopGroup 等待 10 秒,并不影响 nioEventLoopGroup 处理任务。这样就已经将耗时较长的任务隔离开了,达到了线程细分的任务。

总结

到这里 Netty 最精华的部分 EventLoop 就已经介绍完毕了,希望小伙伴们有所收获,这里大明哥简要总结下:

  1. EventLoop 是 Netty 实现 Reactor 线程模型的核心处理引擎。
  2. Netty 推荐采用主从多线程模型,其中 BossEventLoopGroup 负责 ServerSocketChannel 的 Accept 事件,WorkerEventLoopGroup 负责 SocketChannel 的读写事件。
  3. 当客户端与服务端建立连接时,服务端会为该客户端新建一个 Channel,并将该 Channel 与一个 EventLoop 绑定起来,在该 Channel 的整个生命周期中,它所有的 IO 事件都由该 EventLoop 处理。
  4. EventLoop 其本质就是一个单线程任务执行器,它内部维护着一个 Selector ,通过 while(true) 的方式来判断所绑定的 Channel 是否有 I/O 事件发生,如果有则交给 ChannelHandler 处理。
  5. EventLoop 与一个 Thread 绑定,该 EventLoop 所有的 I/O 事件都是由该 Thread 来处理,所以我们需要将耗时较长的逻辑分离出来,以免影响到其他 Channel 的事件处理。
  6. 对应关系如下:
    • 一个 EventLoopGroup 包含一个或者多个 EventLoop。
    • 一个 Channel 在它的声明周期中只会与一个 EventLoop 绑定。
    • 一个 EventLoop 可以绑定多个 Channel。
    • 一个 EventLoop 只会与一个 Thread 绑定。

看到这里可能有小伙伴觉得 EventLoop 有点儿简单,觉得 EventLoop 不就是继承了 Executor 的线程执行器么?这里怪我,因为上面的 EventLoop UML 类图也确实是简单,这里大明哥贴出来一个完整的,各位小伙伴先感受下:

够复杂吧!这里大明哥就不展开阐述了,感兴趣的小伙伴可以先研究研究,后面大明哥会专门写文章来分析这个继承关系的。

源码地址:http://b.nxw.so/1YfuVb

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

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

相关文章

竞赛选题 深度学习疫情社交安全距离检测算法 - python opencv cnn

文章目录 0 前言1 课题背景2 实现效果3 相关技术3.1 YOLOV43.2 基于 DeepSort 算法的行人跟踪 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习疫情社交安全距离检测算法 ** 该项目较为新颖&#xff0c;适合作为竞赛…

SQL 注入漏洞详解

SQL 注入漏洞详解 漏洞描述 sql注入漏洞是指恶意用户在应用与数据库交互的地方利用非法的操作获取数据库内容从以下两点分析: 没有对用户输入的数据进行充分的过滤和验证&#xff0c;导致一些用户利用此漏洞向数据库插入恶意sql语句非法请求数据库从而获得一些敏感数据在与数…

【服务发现与配置】Consul特性及搭建

文章目录 一、前言二、概念2.1、什么是Consul&#xff1f;2.2、Consul具有哪些特点?2.3、Consul 架构图2.4、Consul的使用场景 三、安装3.1. 下载3.2. 解压3.3. 拷贝到usr目录下3.4. 查看 安装是否成功3.5. 启动 四、Consul 开机自启动4.1. 路径/usr/lib/systemd/system/&…

ubuntu 安装redis详细教程

下载redis安装包 链接如下&#xff1a; http://redis.io/download 本例版本为&#xff1a;redis-7.2.3.tar.gz 下载安装包到目录/opt下&#xff0c;路径可修改&#xff0c;本例为/opt wget https://github.com/redis/redis/archive/7.2.3.tar.gz 解压安装包&#xff0c;并…

操作系统 day09(线程)

线程 为什么引入线程 在没引入进程之前&#xff0c;系统中的各个程序只能串行的执行&#xff0c;比如&#xff1a;只能先听歌&#xff0c;再聊QQ。引入进程之后&#xff0c;各个程序可以并发执行&#xff0c;比如&#xff1a;一边听歌&#xff0c;一边聊QQ。但是现在QQ可以一…

C++ 图解二叉树非递归中序 + 实战力扣题

leetCode 94.二叉树的中序遍历 94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 算法思路&#xff1a; 总结&#xff1a; 对中序遍历这个例子进行总结&#xff0c;找出打印“中”节点的时刻&#xff0c;来寻找本质。打印的是一棵二叉树的“中”节点&#xff0c…

MySQL -- mysql connect

MySQL – mysql connect 文章目录 MySQL -- mysql connect一、Connector/C 使用1.环境安装2.尝试链接mysql client 二、MySQL接口1.初始化2.链接数据库3.下发mysql命令4.获取执行结果5.关闭mysql链接6.在C语言中连接MySQL 三、MySQL图形化界面推荐 使用C接口库来进行连接 一、…

Python按类别和比例从Labelme数据集中划分出训练数据集和测试数据集

Python按类别和比例从Labelme数据集中划分出训练数据集和测试数据集 前言前提条件相关介绍实验环境按类别和比例从Labelme数据集中划分出训练数据集和测试数据集代码实现输出结果 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#x…

Java算法(六):模拟评委打分案例 方法封装抽离实现 程序的节流处理

Java算法&#xff08;六&#xff09; 评委打分 需求&#xff1a; 在编程竞赛中&#xff0c;有 6 个评委为参赛选手打分&#xff0c;分数为 0 - 100 的整数分。 选手的最后得分为&#xff1a;去掉一个最高分和一个最低分后 的 4个评委的平均值。 注意程序的节流 package c…

聊聊室内导航在应用方面

大家去大型的商场时&#xff0c;应该都见过一些提示牌&#xff0c;微信扫一扫导航。当拿微信扫了之后&#xff0c;就会打开一个小程序&#xff0c;里面显示整个商场的二维或三维的平面结构&#xff0c;以及当前自己的位置。此时可以通过手机快速的查看商场内其他的商铺、公共区…

NAND Vpass对读干扰和IO性能有什么影响?

1.SSD基础知识 SSD的存储介质是什么&#xff0c;它就是NAND闪存。那你知道NAND闪存是怎么工作的吗&#xff1f;其实&#xff0c;它就是由很多个晶体管组成的。这些晶体管里面存储着电荷&#xff0c;代表着我们的二进制数据&#xff0c;要么是“0”&#xff0c;要么是“1”。NA…

C++打怪升级(十)- STL之vector

~~~~ 前言1. vector 是什么2. 见见vector的常用接口函数吧构造函数无参构造函数使用n个val构造拷贝构造使用迭代器范围构造初始化形参列表构造 析构函数赋值运算符重载函数元素访问[]运算符重载函数访问at函数访问front函数back函数 迭代器相关正向迭代器反向迭代器 容量相关si…

2023.11.09 homework (2)

【七年级上数学】 教别人也是教自己&#xff0c;总结下&#xff1a; 13&#xff09;找规律的题目&#xff0c;累加题目&#xff0c;要整体看&#xff0c;不然不容易算出来&#xff0c;求最大值&#xff0c;那么就是【最大值集群和】减去【最小集群和】就是最大值 9-12&#x…

Python进行数据可视化,探索和发现数据中的模式和趋势。

文章目录 前言第一步&#xff1a;导入必要的库第二步&#xff1a;加载数据第三步&#xff1a;创建基本图表第四步&#xff1a;添加更多细节第五步&#xff1a;使用Seaborn库创建更复杂的图表关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Pyth…

离散数学第一章知识点复习

命题&#xff1a;陈述句 真值已经确定 原子命题&#xff08;简单命题&#xff09;&#xff1a;不能被分解为更简单的命题 命题化的时候的解题步骤&#xff1a; 1. 先给出原子命题 2. 符号化 注意蕴含式&#xff1a;记作 p -> q &#xff0c;p是前件&#xff0c;q 是后…

洛谷P5731 【深基5.习6】蛇形方阵java版题解

import java.util.Arrays; import java.util.Scanner;// 给出一个不大于9的正整数n&#xff0c;输出nn的蛇形方阵。 public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();int[][] a new int[n][n];int total…

【Git】Git安装入门使用常用命令Gitee远程仓库上传文件与下载

一&#xff0c;Git入门 1.1 Git是什么 Git是一款分布式版本控制系统&#xff0c;被广泛用于软件开发中的源代码管理。它由Linus Torvalds在2005年创造并发布&#xff0c;旨在解决传统版本控制系统&#xff08;如SVN&#xff09;的一些局限性。主要用于敏捷高效地处理任何或小或…

qframework 架构 (作者:凉鞋)使用笔记

一些准则&#xff1a; 根据VIEW->SYSTEM->MODEL的分层架构 初始架构&#xff1a; app. using FrameworkDesign;namespace ShootingEditor2D&#xff08;项目的命名空间&#xff09; {public class ShootingEditor2D &#xff08;游戏名称&#xff09;: Architecture&l…

vue 子页面通过暴露属性,实现主页面的某事件的触发

目录 1.前言2.代码2-1 子页面2-2 主页面 1.前言 需求&#xff1a;当我在子页面定义了一个定时器&#xff0c;点击获取验证码&#xff0c;计时器开始倒计时&#xff0c;在这个定时器没有走完&#xff0c;退出关闭子页面&#xff0c;再次进入子页面&#xff0c;定时器此时会被刷…

cpu 支持内存带宽与内存最大长度的关系《鸟哥的 Linux 私房菜》

鸟哥的 Linux 私房菜 -- 计算机概论 -- 計算机&#xff1a;辅助人脑的好工具 同理&#xff0c;64 位 cpu 一次接受内存传递的 64bit 数据&#xff0c;内存字节地址用 64 位记录&#xff0c;最多能记录2^64个字节2^64Bytes2^34GB17179869184GB2^24TB&#xff0c;理论上&#xff…