深入理解网络非阻塞 I/O:NIO

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • 非阻塞式 I/O 模型
  • 图解分析
  • 源码实践
    • Socket 服务端代码
    • Socket 客户端代码
    • 流程说明
      • configureBlocking
      • 客户端连接
    • C10K 问题
      • 源码
      • 流程分析
      • 错误排查
  • BIO vs NIO
    • 阻塞的套接字函数调用
    • 两者肉眼可见区别
  • NIO 为什么速度慢?
  • 总结

前言

Unix/Linux 下可用的 I/O 模型有以下五种:

  1. 阻塞式 I/O
  2. 非阻塞式 I/O
  3. I/O 复用(select、poll)
  4. 信号驱动式 I/O(SIGIO)
  5. 异步 I/O

在 Linux 中操作内核时,所有的无非三种操作,分别是输入、输出、报错输出

0-输入
1-输出
2-报错输出

一个输入操作通常包括两个不同的阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字(Socket)的输入操作,第一步通常涉及等待数据从网络中;当所等待分组到达时,它被复制到内核中的某个缓冲区,第二步就是把数据从内核缓冲区复制到应用进程缓冲区

非阻塞式 I/O 模型

进程把一个套接字设置成非阻塞是在通知内核:当所请求的 I/O 操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误

在这里插入图片描述

前三次调用 recvfrom 时没有数据可返回,因此内核转而立即返回一个 EWOULDBLOCK 错误;第四次调用 recvfrom 时已有一个数据报准备好,它被复制到应用进程缓冲区,于是 recvfrom 成功返回,接着处理数据

EWOULDBLOCK:E 是 Error,WOULD BLOCK 是可能会被阻塞的意思
表示当前没有数据可读或没有缓冲区可写,需要等待下一次读写事件再尝试读写,非阻塞模式下可以继续尝试读写

当一个应用进程像这样对一个非阻塞描述符,循环调用 recvfrom 时,我们称之为轮询(Polling);应用进程持续轮询内核,以查看某个操作是否就绪;这么做往往耗费大量 CPU 时间,不过这种模型偶尔也会遇到,通常是在专门提供某一种功能的系统中才有.

图解分析

在这里插入图片描述

  • 当有新的连接进来时,主线程负责执行 accept 连接客户端,clone 出一个子进程新的 sockfd 去 accept/read,等待其他客户端连接时是非阻塞的,读取客户端数据也是非阻塞的,只是返回给客户端是 EWOULDBLOCK 状态
  • NIO 采用的处理方式:主线程阻塞去等待客户端连接,以非阻塞的方式为每个客户端读取数据

NIO 核心的参数设置:configureBlocking(false)

源码实践

Socket 服务端代码

package org.vnjohn.nio.server;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

/**
 * @author vnjohn
 * @since 2023/12/2
 */
public class SocketNIOServer {

    public static void main(String[] args) throws Exception {
        // 链表集合存放所有的 socket client 实例
        LinkedList<SocketChannel> clients = new LinkedList<>();
        // 服务端开启监听:接受客户端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8090));
//        serverSocketChannel.configureBlocking(true);
        // OS  NONBLOCKING 只让接受客户端并不阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("step1: new ServerSocketChannel(8090)");
        while (true) {
            // 接受客户端的连接
            Thread.sleep(1000);
            // 不会阻塞
            // 在操作系统中返回:-1,在 Java 程序中返回 NULL
            SocketChannel socketChannel = serverSocketChannel.accept();
            // accept 调用内核:
            // 1、没有客户端连接进来,返回值:在 BIO 时一直卡/阻塞着,但是在 NIO ,不卡着,返回 -1,NULL
            // 如果来客户端的连接,accept 返回的是这个客户端的 fd5
            if (socketChannel == null) {
                System.out.println(".....");
            } else {
                // 服务端 listen socket <连接请求三次握手后,通过 accept 得到连接的 sockfd>,连接 socket <连接后读写使用>
                socketChannel.configureBlocking(false);
                int port = socketChannel.socket().getPort();
                System.out.println("step2:client\t" + port);
                clients.add(socketChannel);
            }
            // 堆内:allocate -> HeapByteBuffer
            // 堆外:allocateDirect -> DirectByteBuffer
            ByteBuffer buffer = ByteBuffer.allocateDirect(4096);

            // 遍历已经链接进来的客户端读写数据
            // 这里采用串行化的方式进行接收,可以更改为采用 BIO 方式,一个客户端抛出一个线程进行接收处理
            for (SocketChannel client : clients) {
                // 不会阻塞,返回:> 0、-1、0
                int num = client.read(buffer);
                if (num > 0) {
                    buffer.flip();
                    byte[] bufferByte = new byte[buffer.limit()];
                    buffer.get(bufferByte);
                    String b = new String(bufferByte);
                    System.out.println(client.socket().getPort() + " : " + b);
                    buffer.clear();
                }
            }
        }
    }
}

Socket 客户端代码

package org.vnjohn.nio.client;

import java.io.*;
import java.net.Socket;

/**
 * @author vnjohn
 * @since 2023/12/2
 */
public class SocketNIOClient {

    public static void main(String[] args) {
        try {
            Socket client = new Socket("172.16.249.10", 8090);
            client.setSendBufferSize(20);
            client.setTcpNoDelay(true);// false 优化,true 不优化
            client.setOOBInline(false);
            OutputStream out = client.getOutputStream();
            InputStream in = System.in;
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            while (true) {
                String line = reader.readLine();
                if (line != null) {
                    byte[] bb = line.getBytes();
                    for (byte b : bb) {
                        out.write(b);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

流程说明

172.16.249.10 是之前作为 node1 节点所在 IP

将以上两个 java 源文件上传到 node1 虚拟节点上,所在目录:/opt/java

1、在虚拟节点上安装好 Java 环境
2、将源文件所在的 package 包名,通过 vim 命令将 package 包名删除首行.
3、将 Java 源文件进行编译为 .class 文件 > javac SocketServer.java、javac SocketClient.java

configureBlocking

追踪应用程序与操作系统中的交互信息

cd /opt/java
strace -ff -o out java SocketNIOServer

正常的流程都是先 socket()、bind()、listen()、accept()

关于这四个函数的详细介绍可以阅读博主「网络 I/O」专栏中的另外一篇博文:
深入理解网络阻塞 I/O:BIO

先设置 configureBlocking = true,代表当前设置 SocketChannel 是阻塞式运行的

若设置 configureBlocking = true 时,观察追踪到操作系统的操作信息,能够详细看到我们的操作系统基于内核是一个阻塞态:

accept(4,:说明当前程序是阻塞运行的

在这里插入图片描述

再设置 configureBlocking = false,代表当前设置 SocketChannel 是非阻塞式运行的

accept(4, 0x7f8c0d4ee0, [28]) = -1 EAGAIN (Resource temporarily unavailable)
在操作系统侧,返回的是 -1,说明是非阻塞运行的

在这里插入图片描述

按照代码执行的逻辑来看,控制台会一直打印:,说明当前 SocketChannel 是非阻塞运行的.

在这里插入图片描述

说明通过设置 configureBlocking = false 就可以实现不阻塞,若没有客户端进行连接在操作系统中返回的是 -1,而在 Java 中返回是 null

客户端连接

在 node2 节点:172.16.249.10,运行 SocketNIOClient 程序代码

1、移除首行 package 包名
2、cd /opt/java,编译 Java 源文件:javac SocketNIOClient.java
3、运行 Java 可执行程序:java SocketNIOClient

在这里插入图片描述

当客户端连接以后,就会在 out.pid 文件中分配一个文件描述符给到当前这个客户端,如下:

accept(4, {sa_family=AF_INET6, sin6_port=htons(32972), inet_pton(AF_INET6, “::ffff:172.16.249.11”, &sin6_a ddr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 5

当在客户端中发送数据时,比如:123456,在 out.pid 文件中会触发系统调用 R/W 读写

在这里插入图片描述

在这里插入图片描述

在 out.pid 文件中,会发现大量的 EAGAIN 字眼,代表当次资源暂不可用,这个操作可能等下次重试后可用

EAGAIN 官方定义:“Resource temporarily unavailable.” The call might work if you try again later. The macro EWOULDBLOCK is another name for EAGAIN; they are always the same in the GNU C Library.

EWOULDBLOCK 官方定义:“Operation would block.” In the GNU C Library, this is another name for EAGAIN (above). The values are always the same, on every operating system.

两者都代表含义是一样的,在 GUN C 库中,EWOULDBLOCK 的另外一个名称称之为 EAGAIN

C10K 问题

当 C10K 出现时,若有 1W 个客户端建立连接,在 BIO 时需要抛出 1W 个线程,此时就会造成资源消耗越多,任务调度就会变得越多,内核态用户态之间的切换也会越多

当在 NIO 时进行使用,在内核中又会出现另外一个问题,接着向下分析

源码

package org.vnjohn.nio.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

/**
 * @author vnjohn
 * @since 2023/12/2
 */
public class C10KClient {

    public static void main(String[] args) {
        LinkedList<SocketChannel> clients = new LinkedList<>();
        InetSocketAddress serverAddr = new InetSocketAddress("172.16.249.10", 8090);
        // 端口号的问题:65535
        for (int i = 10000; i < 25000; i++) {
            try {
                SocketChannel client = SocketChannel.open();
                //  172.16.249.10:10000   172.16.249.10:8090
                client.bind(new InetSocketAddress("1172.16.249.10", i));
                client.connect(serverAddr);
                clients.add(client);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        for (int i = 25000; i < 50000; i++) {
            try {
                SocketChannel client = SocketChannel.open();
                //  172.16.249.10:25000   172.16.249.10:8090
                client.bind(new InetSocketAddress("172.16.249.10", i));
                client.connect(serverAddr);
                clients.add(client);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("total clients:"+ clients.size());
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

修改 SocketNIOServer 服务端代码,将两行代码注释

在这里插入图片描述

其目的是为了让服务端能够快速与客户端建立连接,而不是 1s 处理一个客户端连接,以便我们在 Linux 中来分析最大连接数

流程分析

1、先将 SocketNIOServer 休整的代码,重新编译执行:strace -ff -o out java SocketNIOServer

2、先 C10K 问题的客户端代码编译为 class 文件,然后通过 java 运行

3、接着观察几个情况

首先是观察 socket、TCP/IP 条目信息,可以看到有很多条类目,客户端->服务端之间的二元组信息「IP:端口」

在这里插入图片描述

观察服务端接收客户端连接的情况,是否保持正常

在这里插入图片描述

客户端在满足条件时,bind 客户端是处于正常状态的,而一旦超过了内核所配置的最大的大小以后,会发现会出现一个 Error 错误信息

java.net.SocketException: Too many open files

在这里插入图片描述

从异常信息上来看,意思是文件描述符超过了限制的大小.

错误排查

该错误是可以被修复的,但是修复了意义上不大,这种非阻塞运行的方式对内核这块的资源损耗还是很大的

java.net.SocketException: Too many open files

可打开的文件描述符大小是受内核配置的限制的,但它也是支持可配置的,查看命令:ulimit -a 或 ulimit -n

在这里插入图片描述

open files = 1024:一个进程最多可以打开 1024 个文件描述符

可通过 ulimit -SHn Xxx 命令来临时调整大小

  • S:soft 软
  • H:hard 硬
  • n:文件描述符

调整后可以通过:ulimit -n 再查看修改后的大小

在内核中会根据上限的物理内存估算出一个总文件描述符大小

[root@node1 java]# cat /proc/sys/fs/file-max 
146266

BIO vs NIO

套接字的默认状态是阻塞的,当你未设置参数 configureBlocking 时,这就意味着当发出一个不能立即完成的套接字调用时,其进程被投入睡眠状态,等待相应操作完成

阻塞的套接字函数调用

可能阻塞的套接字调用可分为以下四类:

  1. 输入操作,包括:read、readv、recv、recvfrom、recvmsg 五个函数

既然 TCP 是字节流协议,该进程的唤醒只要有一些数据到达,这些数据即可能是单个字节,也可以是一个完整的 TCP 分节中的数据,若想等到某个固定的数目的数据可读为止,需要调用的 readn 函数或者指定 MSG_WAITALL 标志

既然 UDP 是数据报协议,若一个阻塞的 UDP 套接字的接收缓冲区为空,对它调用输入函数的进程将被投入睡眠,直到有 UDP 数据报到达

若某个进程对一个阻塞的 TCP 套接字调用这些输入函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被投入睡眠,直到有一些数据到达

对于非阻塞的套接字,若输入操作不能被满足(对于 TCP 套接字即至少有一个字节的数据可读,对于 UDP 套接字即有一个完整的数据报可读)响应调用将立即返回一个 EWOULDBLOCK |EAGAIN 错误

  1. 输出操作,包括:write、writev、send、sendto、sendmsg 五个函数,对于一个 TCP 套接字,内核将从应用进程的缓冲区到该套接字的发送缓冲区复制数据

对于阻塞的套接字,若其发送缓冲区没有空间,进程将投入睡眠,直到有空间为止

对于一个非阻塞的 TCP 套接字,若其发送缓冲区中根据没有空间,输出函数调用将立即返回一个 EWOULDBLOCK |EAGAIN 错误,若其发送缓冲区有一些空间,返回值将是内核能够复制到该缓冲区中的字节数,该字节数也称之为不足计数

  1. 接受外来连接,即 accept 函数

若对一个阻塞的套接字调用 accept 函数,并且尚无新的连接到达,调用进程将被投入睡眠

若对一个非阻塞的套接字调用 accept 函数,并且尚无新的连接到达,accept 调用将立即返回一个 EWOULDBLOCK |EAGAIN 错误

  1. 发起外出连接,即用于 TCP 的 connect 函数,connect 同样可用于 UDP,不过它不能使一个 “真正” 连接建立起来,它只是使内核保存对端的 IP 地址、端口号

TCP 连接建立涉及到一个三次握手过程,而且 connect 函数一直要等到客户收到对于自己的 SYN -> ACK 为止才返回,这意味着 TCP 的每个 connect 总会阻塞其调用进程至少有一个到服务器的 RT 时间

若对于一个非阻塞的 TCP 套接字调用 connect,并且连接不会立即建立,连接的建立能照样发起(臂如:送出 TCP 三次握手的第一个分组)不过会返回一个 EINPROGRESS 错误

两者肉眼可见区别

BIO 会在主线程阻塞式的去接受一个 socket 客户端的连接,在操作系统内核中不会返回 -1,一直卡着不动: accept(4,

客户端读取数据时也是阻塞式的,没有 -1、0、>0,所以在使用 BIO 时要单独开辟新的线程去专门读取来自客户端的数据,而主线程只是阻塞式的负责接收来自客户端的连接;当客户端的连接很多时,线程的数量就会很多,线程之间的切换和任务频繁的调度就显而易见了.

主线程阻塞式接收客户端的连接:accept
每一个客户端有一个子线程负责去接收客户端的数据:read、readv

在 NIO 中,可配置式的支持配置阻塞或非阻塞,通过:configureBlocking 参数来进行配置,true:阻塞、false:非阻塞

  1. accept 调用了操作系统内核有以下两种情况

当没有客户端连接进来时,在 BIO 时会一直阻塞着,但是在 NIO 时,操作系统内核层面会返回 -1,而在 Java 应用程序中会返回 NULL

当有客户端连接进来时,accept 返回的是分配给这个客户端的文件描述符 socketfd,Java 中返回的是一个 SocketChannel 对象

  1. socket 对象分为以下两种:

服务端 ServerSocketChannel:连接请求三次握手完成后,该对象可以通过 accept 方法获取到客户端的 socket -> SocketChannel

客户端 SocketChannel:客户端与服务端之间建立好连接以后,通过该 socket 来负责读写数据使用

两者 socket 都需要配置为非阻塞式运行,才能够保证连接、读取数据都是非阻塞运行的!!!

NIO 为什么速度慢?

NIO 优势在于可以通过一个或多个线程来解决 N 个 IO 连接阻塞的问题

它的问题在于:在本篇博文通过 C10K 问题来模拟当其到达了 1W 个客户端,但是每次进行读取时都会循环一次,会带有 O(n) 复杂度的 recv 系统函数调用,可能在这些客户端中只有几个客户端是有数据的额,所以在这期间会有很多系统函数调用是没有意义的,浪费的只是系统的资源以及给操作系统内核带来没必要的压力

所以,从 NIO 来看,应该考虑的是只做那么有必要的事,没必要的事应该尽量的去避免它发生

总结

该篇博文主要介绍的是 I/O 模型中的非阻塞 I/O -> NIO,简要分析了 NIO 非阻塞式 I/O 简要的模型,通过图解分析的方式告知它与 BIO 之间的区别,通过实践代码的方式来分析非阻塞 I/O 在系统调用中所涉及到的流程,同时也介绍了 C10K 问题给非阻塞式 I/O 带来的不利之处,最后介绍了上篇 BIO 博文与 NIO 之间的相关的区别以及 NIO 为什么速度会慢的原因,希望能够得到你的支持,感谢三连

四元组唯一:源 IP、源端口、目标 IP、目标端口

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

非标设计之螺纹螺丝选型二

目录 一、螺丝的表面处理工艺&#xff1a;镀锌工艺&#xff1a;渗锌工艺&#xff1a;热浸锌工艺&#xff1a;达克罗工艺&#xff1a;镀镍工艺&#xff1a;氧化&#xff08;发黑&#xff09;工艺&#xff1a;电泳黑工艺&#xff1a;不锈钢螺钉&#xff1a; 二、按照颜色分工艺&a…

掌握视频剪辑技巧,轻松自定义视频速率,打造个性化出彩视频

你是否曾经因为视频节奏平淡而缺乏吸引力而苦恼&#xff1f;现在&#xff0c;我们为你推荐一款视频批量剪辑工具&#xff0c;让你轻松自定义视频速率&#xff0c;实现出彩个性化视频。 首先第一步&#xff0c;我们要打开好简单批量智剪&#xff0c;并登录账号。 第二步&#x…

三十五、Seata的基本架构、部署TC服务、微服务集成Seata

目录 一、基本架构 1、Seata事务中的三个重要角色 2、四种不同的分布式事务解决方案&#xff1a; 二、TC的部署 三、微服务集成Seata 1、引入Seata相关依赖 2、配置yml文件 3、启动服务 一、基本架构 Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决…

centos7 yum安装mysql5.7

1.获取源 wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm 2.安装源 yum -y install mysql57-community-release-el7-11.noarch.rpm 3.安装mysql yum -y install mysql-server 4.如果出现下面错误&#xff0c;没有错误就忽略 使用以下命令解决…

如何在Rocky Linux中安装nmon

一、环境基础 [rootlocalhost nmon16d]# cat /etc/redhat-release Rocky Linux release 9.2 (Blue Onyx) [rootlocalhost nmon16d]# uname -r 5.14.0-284.11.1.el9_2.x86_64 [rootlocalhost nmon16d]# 二、安装步骤 在Rocky Linux和AlmaLinux等基于RHEL 的发行版上&#xff…

把握生成式AI新机遇,亚马逊云科技助力下一位独角兽

文章目录 前言亚马逊云科技生成式AI创业热潮向应用与工具链集中生成式AI初创生而全球化 赛道更细分、布局更广阔后记 前言 DoNews11月20日消息&#xff0c;当一项新技术出现&#xff0c;并成为行业主流甚至是变革的“敲门砖”时&#xff0c;企业应该如何应对&#xff1f; 202…

Zookeeper 安装与部署

Zookeeper官网 目录 1 配置文件参数解读2 Zookeeper 单点安装3 Zookeeper 分布式安装 1 配置文件参数解读 Zookeeper 中的配置文件 zoo.cfg 中参数含义解读如下&#xff1a; &#xff08;1&#xff09;tickTime 2000&#xff1a;通信心跳数&#xff0c;Zookeeper 服务器与客户…

Shutdown Signal: channel error; protocol method: #method<channel.close>

完整异常信息&#xff1a; Shutdown Signal: channel error; protocol method: #method<channel.close>(reply-code404, reply-textNOT_FOUND - no exchange fanoutExchange in vhost /, class-id60, method-id40) 意思是找不到名字是 fanoutExchange 的虚拟机 就是虚拟机…

增强现实技术革新零售业:提升购物体验的未来技术

增强现实&#xff08;AR&#xff09;技术正在改变零售业的面貌&#xff0c;为消费者提供了全新的购物体验。本文将探讨AR技术在零售行业中的应用&#xff0c;以及它如何改变传统的购物方式。 首先&#xff0c;AR技术允许消费者在现实世界中查看虚拟的产品展示。在服装和家具行业…

基于51单片机的交通灯_紧急开关+黄灯倒计时+可调时间

51单片机交通灯_紧急开关黄灯倒计时可调时间 开题报告系统硬件设计主控制器选择系统硬件结构图时钟及复位电路指示灯及倒计时模块 倒计时模块&#xff1a;程序软件主流程框架main函数 设计报告资料清单资料下载链接 基于51单片机交通灯_紧急开关黄灯倒计时可调时间 仿真图prote…

【DPDK】Trace Library

概述 跟踪是一种用于了解运行中的软件系统中发生了什么的技术。用于跟踪的软件被称为跟踪器&#xff0c;在概念上类似于磁带记录器。记录时&#xff0c;放置在软件源代码中的特定检测点会生成保存在巨大磁带上的事件&#xff1a;跟踪文件。稍后可以在跟踪查看器中打开跟踪文件…

【U8+】用友U8删除固定资产卡片,提示:当前卡片不是本月录入的卡片,不能删除。

【问题描述】 用友U8软件&#xff0c;参照已有账套新建账套的时候&#xff0c;选择结转期初余额。 例如&#xff1a;参照已有账套的2022年新建2023年的账套。 结转期初的时候勾选了固定资产模块&#xff0c; 建立成功后登录23年新的账套后&#xff0c;删除固定资产卡片&#xf…

17.字符串处理函数——字符串比较函数

文章目录 前言一、题目描述 二、解题 程序运行代码 总结 前言 本系列为字符串处理函数编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 二、解题 程序运行代码 #include<stdio.h> #include<string.h> int main() {char *str1 "hello wo…

【C++ regex】C++正则表达式

文章目录 前言一、正则表达式是什么&#xff1f;二、<regex>库的基础使用2.1 第一个示例2.1 <regex>库的函数详解std::regex_matchstd::regex_searchregex_search 和 regex_match 的区别std::regex_replacestd::regex_iterator 和 std::sregex_iterator&#xff1a…

谭巍主任探讨:丝状疣感染机制揭秘

丝状疣是寻常疣的一种特殊类型&#xff0c;主要由人乳头瘤病毒(HPV)感染所致。HPV是一种常见的病毒&#xff0c;可以通过直接接触传播&#xff0c;也可以通过间接接触传播。而多年临床经验告诉北京劲松中西医医院皮肤性病科主任谭巍丝状疣的感染通常与以下因素有关&#xff1a;…

数据结构和算法-树与二叉树的存储结构以及树和二叉树和森林的遍历

文章目录 二叉树的存储结构二叉树的顺序存储二叉树的链式存储小结 二叉树的先中后序遍历例题小结 二叉树的层次遍历小结 由遍历序列构造二叉树一个遍历序列即使给定了前中后序&#xff0c;也不能确定该二叉树的形态可以确定的序列组合前序中序后序中序层序中序 小结若前序&…

6 Redis缓存设计与性能优化

缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c; 缓存层和存储层都不会命中&#xff0c; 通常出于容错的考虑&#xff0c; 如果从存储层查不到数据则不写入缓存层。缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c; 失去了缓存保护后端存储的意义…

整数分频,奇偶分频。

实验目标&#xff1a; 实现任意整数奇偶分频。 /* 二分频电路就是用同一个时钟信号通过一定的电路结构转变成不同频率的时钟信号。 二分频就是通过有分频作用的电路结构&#xff0c;在时钟每触发2个周期时&#xff0c;电路输出1个周期信号。 比如用一个脉冲时钟触发一个计…

numpy实现神经网络

numpy实现神经网络 首先讲述的是神经网络的参数初始化与训练步骤 随机初始化 任何优化算法都需要一些初始的参数。到目前为止我们都是初始所有参数为0&#xff0c;这样的初始方法对于逻辑回归来说是可行的&#xff0c;但是对于神经网络来说是不可行的。如果我们令所有的初始…

如何学习 Spring ?学习 Spring 前要学习什么?

整理了一下Spring的核心概念BeanDefinitionBeanDefinition表示Bean定义&#xff0c;BeanDefinition中存在很多属性用来描述一个Bean的特点。比如&#xff1a;class&#xff0c;表示Bean类型scope&#xff0c;表示Bean作用域&#xff0c;单例或原型等lazyInit&#xff1a;表示Be…