什么是高效的IO?

正常情况下,IO=等+拷贝
高效的IO=拷贝(即让IO尽量不等)

为什么我们平常玩电脑的时候,感觉不到等待的过程呢?
任何通信场景,IO通信场景,效率一定是有上限的. 花盆里,长不出参天大树。也就是说任何通信场景下的IO都有等待的过程,只是因为我们的本地主机硬件彼此之间都离得很近,等待的过程很短,所以我们感觉不到,你换做网络通信,通信双方离千里之外,你就很能感觉到这个等待的过程了。

如何提高IO的效率?

单位时间内,等待的比重越低,IO 效率越高!
提高IO的效率,其实就是要提高拷贝操作在IO操作中的比重,减少等待的比重!

五种IO模型

五种IO模型分别是哪五种?他们有啥区别?

  1. 阻塞式IO
  2. 非阻塞IO轮询
  3. 信号驱动式IO
  4. 多路转接/多路复用IO
  5. 异步IO

我们可以通过下面的例子去理解:
假如说在你老家有一个池塘,然后有很多人喜欢去这个池塘里面钓鱼。
在一个风和日丽的下午,有俩人在池边钓鱼,一个叫张三,一个叫李四。这哥俩的钓鱼装备都一样,但是他们钓鱼的方式有些许差别:张三在鱼上钩之前一直在观察鱼竿有没有动静,如果发现没有动静,那他也不干别的事情,就接着等,一直等到有动静为止。而李四并不是一直在观察鱼竿有没有动静,他只是定时的过来查看一下,如果检测到鱼竿没有动静。他就会去做别的事情,然后过一段时间再来看看鱼竿有没有动静。

过了一会儿又来了一个人叫王五,他走到池塘边放杆之后,在鱼竿的头部系了一个铃铛,然后把杆子往那儿一放,自己低头玩手机。每当铃铛响的时候,王五就起来收杆,收完杆之后把杆子一放,继续玩手机。

又过了一会儿,来了一个人叫赵六,这个人很有钱。他沿着池塘的边儿放了100根鱼竿儿,自己就围着池塘来回巡逻,巡逻的过程中一看见哪个鱼竿有鱼上钩了,他赶紧就去收。

最后一个赶来的人叫田七,这个人是个大老板,平时事务非常繁忙,但是就喜欢钓鱼,来到这里之后还没钓一会儿来,这时候突然公司里有急事儿,钓不了鱼了。他就吩咐他的司机小王说,小王你给我钓一下。今天下午你钓完10条鱼以后,给我打个电话。我过来开始接你回去,完不成任务,你就一直搁这给我调。

在这里插入图片描述
在上面的例子中,你如果将钓鱼这件事情理解成IO。将钓鱼的人理解成计算机中的进程,那些鱼竿儿理解成Io的目标文件(文件描述符)。就可以比较好的理解。五种不同的io方式之间的区别。其实我们可以看到这5个人的做法应该是一个比一个高效的,因为他们从上往下等待的时间越来越少。钓鱼过程中自己腾出来的时间越来越多

问题1:阻塞式IO 与 非阻塞式IO的区别在哪里?(张三和李四的做法有什么区别?)

张三是在鱼上钩之前一直在观察鱼竿有没有动静,如果发现没有动静,那他就接着等,一直等到有动静为止。而李四并不是一直在观察鱼竿有没有动静,他只是定时的过来查看一下,如果检测到鱼竿没有动静。他就会去做别的事情,然后过一段时间再来看看鱼竿有没有动静。

这俩人的做法的核心区别在于,当他们检测到鱼竿没有动静的时候,他们的处理方式是不一样的,张三的处理方式是没有等到我接着等。而李四的处理方式是没有等到,我就去干别的事儿,过一会儿我再来看。

阻塞式IO与非阻塞式IO的核心区别也在于此,阻塞式IO如果没等到,就会一直在等,而非阻塞式IO如果没等到,他不会一直等,而是回去干别的事情,过一段时间之后再来检测。

但是值得注意的是,张三和李四在鱼没有上钩之前干了什么,鱼一旦上钩,他们干的事情都是一样的,也就是说——阻塞式IO 与 非阻塞式IO 拷贝操作的效率没有任何区别!

问题2:如何理解 非阻塞式IO 的效率比 阻塞式IO 要高 ?

我们经常会听到一种说法叫做:非阻塞式IO 的效率比 阻塞式IO 要高。这个效率应该如何理解?是不是意味着相同的时间内李四钓的鱼比张三钓的鱼多呢?
其实并不是,这两个人钓到鱼的多少取决于池塘里边的鱼咬谁的钩咬的多。但是在池塘里的鱼看来,我又不知道张三和李四在我没咬钩的时候在干什么?我看到的水底下的两个钩子是差不多的。那我咬他们两个钩的概率就是一样的。那按照这个道理。同样的时间内李四钓到的鱼应该和张三钓的鱼一样多。
也就是说对于同一个IO任务来说,计算机无论是采用 非阻塞式IO 的策略,还是 阻塞式IO 的策略,可能处理这一个任务的时间都是相同的

既然如此,那为什么我们还说非阻塞式IO 的效率比 阻塞式IO 要高呢?我们可以从下面两种角度去理解。
(1)计算机除了要处理io,还要进行很多其他的操作。在相同的时间内,计算机采用非阻塞式IO 的方案进行处理,完成的总工作量要比采用阻塞式IO要多。
李是在下午钓鱼的这段时间内。不仅钓上鱼他还看了很多小说,刷了很多视频。是张三在这个下午完成的工作仅仅是钓了这么多鱼。因此李四完成的总任务比张还要多,因此我们说李四的效率比张三高。

(2)采用非阻塞式IO ,在没有等到IO事件之前。计算机可以去做别的事情,这个别的事情也可以是其他类型的IO,这样采用非阻塞式IO,计算机处理的全部IO工作量就比阻塞式IO多了

用我们的例子去理解,“非阻塞IO效率高” 他的意思是—— 李四可以在同样的时间,做更多的其他事情!

问题3:上面的五种IO,谁的效率是最高的? (这一下午这5个人谁钓的鱼最多?)

赵六!!下午这5个人全部加入之后,池塘里边一共有104根儿鱼竿儿。其中100根鱼竿儿都是赵六的!除了赵六之外,其他的所有人钓鱼都只用一根鱼竿。我们假设这池子里面的鱼咬每一根鱼竿儿的概率是一样的,那傻子都知道这一下午肯定是赵六钓的鱼是最多的。

那么在计算机中,多路转接/多路复用IO的效率就是最高的,也就是说在相同的时间内,采用这种方式处理的IO工作量是最多的。

赵六, 任意一个鱼竿(fd), 鱼(数据)就绪的概率很高 IO = 等+拷贝
一个人, 检测多个鱼竿, 降低了等的比重

问题3:信号驱动式IO最大的特点是什么?效率咋样?(王五的做法和前面俩人的做法最本质的区别在哪里?)

张三就是一直在主动的检测这个鱼有没有上钩。李四虽然经常去干别的事情,但他也会定期去看看这个鱼竿。有没有动静,他心里至少还挂念着这个鱼竿。但是王五他是真一点儿都不挂念,全程低头玩手机,如果不是有这个铃铛叫,他是绝对不会主动抬头的。

我们前面讲IO的工作分成等待和拷贝两个部分。
在信号驱动式IO中,拷贝工作的开始是由信号触发的,也就是说你不给我发信号,我永远都不会开始拷贝。

问题4:这五种IO方式中,最后一种叫做异步IO,那有没有同步IO呢?同步IO的定义是啥?信号产生不是异步的嘛?为什么信号驱动式IO属于同步IO呢?

我们说的五种IO方式中,前面四种都是同步IO。

结合我们前面举的例子,张三李四王五赵六这4个人。他们虽然在鱼上钩之前,等待的方式不一样。但是当鱼上钩的时候,他们都会亲手握住鱼竿儿把鱼拉上来。

对应在计算机中,虽然前四种IO方式的策略不同,但是当等待的IO事件发生的时候,都是由等待这个IO的进程亲自去处理这个IO。而异步io指的是?我专门儿创建一个进程去处理这个IO,我后边儿就一点儿都不问了。当IO完成的时候,让那个进程自动把我要的东西给我。

问题5:同步IO和异步IO的区别是什么?(田七和前面四个钓鱼佬的区别是什么?)

结合我们刚刚说的例子。那4个人你别管他们等待的方式是什么的,但是他们都是在那鱼塘边儿待了一下午,都是亲自拉杆儿把鱼钓上来的。而田七调到一半他就跑了,就去当甩手掌柜去了。这个就很像我们现实生活中黑心煤矿的老板。你别看他的公司干的是煤矿,但是这个公司的中高层有百分之八九十都没下过矿,他们都不知道这个煤是怎么挖的,他们也不知道煤矿有多危险,他们只知道工人冒着生命危险提取出来的煤矿,可以卖掉赚大钱。领导就是只负责给下属提要求,他告诉下属,我不管你们是怎么实现的,反正我只负责验收,你们看着办吧。

我们前面说,IO=等+拷贝
只要你参与了IO的过程(可能你只参与了等的过程,可能你只参与了拷贝的过程,这都算参与IO的过程),就是同步IO。像煤老板那种连煤矿都没下过的,全程不参与IO过程的就是异步IO。

五种IO模型的实现方式

我们以UDP通信中,用户调用recvfrom接收数据的场景为例,说明五种IO模型的不同实现方式

阻塞式IO

这个最简单,只需要正常调用recvfrom就行了(因为创建套接字时默认就是阻塞方式),阻塞式IO的基本过程如下
在这里插入图片描述
下面的通过socket网络编程实现简单阻塞式IO的代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>int main() {// 1. 创建 UDP Socket(默认即为阻塞模式)int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket 创建失败");return -1;}// 2. 绑定地址和端口(作为服务器必须绑定)struct sockaddr_in local_addr;memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;         // IPv4local_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡local_addr.sin_port = htons(8888);       // 端口 8888if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {perror("绑定端口失败");close(sockfd);return -1;}printf("服务器启动,等待数据(阻塞模式)...\n");// 3. 阻塞式接收数据(关键:未收到数据会一直卡在这里)char buf[1024] = {0};struct sockaddr_in client_addr;          // 存储客户端地址socklen_t client_len = sizeof(client_addr);// 调用 recvfrom:若没有数据,进程会进入休眠状态(阻塞)ssize_t recv_len = recvfrom(sockfd, buf, sizeof(buf) - 1,  // 留一个位置给字符串结束符0, (struct sockaddr*)&client_addr, &client_len);if (recv_len < 0) {perror("接收数据失败");close(sockfd);return -1;}// 输出收到的数据和客户端信息buf[recv_len] = '\0';  // 手动添加字符串结束符printf("收到来自 %s:%d 的数据:%s\n",inet_ntoa(client_addr.sin_addr),  // 客户端 IPntohs(client_addr.sin_port),      // 客户端端口buf);                             // 数据内容close(sockfd);return 0;
}

非阻塞式IO

非阻塞IO的处理过程

在这里插入图片描述
具体实现原理也很简单,只需要在创建套接字时将其设置为非阻塞模式即可

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int main() {// 1. 创建 UDP Socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket failed");return -1;}// 2. 设置为非阻塞模式int flags = fcntl(sockfd, F_GETFL, 0);fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);// 3. 绑定地址(可选,若作为服务端需要绑定)struct sockaddr_in local_addr;memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;local_addr.sin_port = htons(8888);if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {perror("bind failed");close(sockfd);return -1;}// 4. 模拟非阻塞 recvfromchar buf[1024] = {0};struct sockaddr_in peer_addr;socklen_t peer_len = sizeof(peer_addr);// 首次调用:数据未准备好时直接返回错误ssize_t ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&peer_addr, &peer_len);if (ret < 0) {// 非阻塞特有错误码:EWOULDBLOCK/EAGAINif (errno == EWOULDBLOCK || errno == EAGAIN) {printf("数据未准备好,非阻塞直接返回\n");} else {perror("recvfrom error");}}// 5. 模拟「重试」或结合多路复用(如 select/poll/epoll)//    这里简化为休眠 2 秒,假设期间有数据到达sleep(2);// 再次调用 recvfrom(假设此时数据已准备好)ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&peer_addr, &peer_len);if (ret > 0) {printf("收到数据:%s (来自 %s:%d)\n", buf, inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port));} else {perror("再次 recvfrom 失败");}close(sockfd);return 0;
}

信号驱动式IO

下面是信号驱动式IO的流程图
在这里插入图片描述
他具体实现起来的思想也很简单,由于在这种方式中IO事件是靠信号递达的,我们就在信号处理函数handle中调用recvfrom进行数据拷贝就行

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>int sockfd;  // 全局套接字描述符,供信号处理函数使用
struct sockaddr_in client_addr;
socklen_t client_len;// 信号处理函数:当内核通知IO事件就绪时被调用
void sigio_handler(int signo) {char buf[1024] = {0};ssize_t recv_len;// 读取数据(此时数据已就绪,不会阻塞)recv_len = recvfrom(sockfd, buf, sizeof(buf)-1, 0,(struct sockaddr*)&client_addr, &client_len);if (recv_len < 0) {perror("recvfrom failed");return;}buf[recv_len] = '\0';printf("收到来自 %s:%d 的数据: %s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);// 简单回复客户端const char* reply = "已收到数据";sendto(sockfd, reply, strlen(reply), 0,(struct sockaddr*)&client_addr, client_len);
}int main() {struct sockaddr_in server_addr;struct sigaction sa;// 1. 创建UDP套接字sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 绑定服务器地址和端口memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(8888);if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 3. 设置信号处理函数(捕获SIGIO信号)sa.sa_handler = sigio_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;if (sigaction(SIGIO, &sa, NULL) < 0) {perror("sigaction failed");close(sockfd);exit(EXIT_FAILURE);}// 4. 设置套接字属主,让内核知道该向哪个进程发送SIGIO信号if (fcntl(sockfd, F_SETOWN, getpid()) < 0) {perror("fcntl F_SETOWN failed");close(sockfd);exit(EXIT_FAILURE);}// 5. 启用信号驱动式IO(设置O_ASYNC标志)int flags = fcntl(sockfd, F_GETFL, 0);if (fcntl(sockfd, F_SETFL, flags | O_ASYNC) < 0) {perror("fcntl F_SETFL O_ASYNC failed");close(sockfd);exit(EXIT_FAILURE);}printf("信号驱动式IO服务器启动,端口 8888...\n");printf("等待数据中(主线程可执行其他任务)...\n");// 6. 主线程可以执行其他任务,无需阻塞等待IOwhile (1) {// 模拟主线程处理其他业务sleep(1);// printf("主线程正在执行其他任务...\n");}close(sockfd);return 0;
}

IO多路转接

操作系统给我们提供了专门的接口用于实现IO多路转接,我们只需要学会如何使用就行了
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h>#define MAX_EVENTS 100
#define BUFFER_SIZE 1024// 设置套接字为非阻塞模式
void set_nonblocking(int fd) {int flags = fcntl(fd, F_GETFL, 0);if (flags == -1) {perror("fcntl F_GETFL");exit(EXIT_FAILURE);}if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {perror("fcntl F_SETFL");exit(EXIT_FAILURE);}
}int main() {// 创建监听套接字int listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd == -1) {perror("socket");exit(EXIT_FAILURE);}// 绑定地址和端口struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(8888);if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind");close(listen_fd);exit(EXIT_FAILURE);}// 开始监听if (listen(listen_fd, 5) == -1) {perror("listen");close(listen_fd);exit(EXIT_FAILURE);}// 创建 epoll 实例int epoll_fd = epoll_create1(0);if (epoll_fd == -1) {perror("epoll_create1");close(listen_fd);exit(EXIT_FAILURE);}// 注册监听套接字到 epollstruct epoll_event event;event.events = EPOLLIN;event.data.fd = listen_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {perror("epoll_ctl add listen_fd");close(listen_fd);close(epoll_fd);exit(EXIT_FAILURE);}struct epoll_event events[MAX_EVENTS];while (1) {// 等待事件发生,最多等待 MAX_EVENTS 个事件int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (num_events == -1) {perror("epoll_wait");break;}for (int i = 0; i < num_events; i++) {if (events[i].data.fd == listen_fd) {// 有新的客户端连接请求struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len);if (client_fd == -1) {perror("accept");continue;}// 设置客户端套接字为非阻塞模式set_nonblocking(client_fd);// 注册客户端套接字到 epollevent.events = EPOLLIN;event.data.fd = client_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {perror("epoll_ctl add client_fd");close(client_fd);}printf("新客户端连接: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));} else if (events[i].events & EPOLLIN) {// 客户端有数据可读int client_fd = events[i].data.fd;char buffer[BUFFER_SIZE];ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);if (bytes_read == -1) {perror("recv");close(client_fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);} else if (bytes_read == 0) {// 客户端关闭连接printf("客户端断开连接: %d\n", client_fd);close(client_fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);} else {buffer[bytes_read] = '\0';printf("收到客户端 %d 数据: %s\n", client_fd, buffer);// 简单回显数据给客户端if (send(client_fd, buffer, bytes_read, 0) == -1) {perror("send");close(client_fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL);}}}}}close(listen_fd);close(epoll_fd);return 0;
}

异步IO

简单来说就是,用户进程将这个IO的任务交给内核,内核把数据拷贝完成之后,再通知应用程序(在信号驱动式IO中,内核通过信号告知应用程序何时可以开始拷贝数据,拷贝数据这活还是得用户进程自己来)
在这里插入图片描述
实现代码

#include <iostream>
#include <fcntl.h>
#include <aio.h>
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <errno.h>// 缓冲区大小
#define BUFFER_SIZE 1024// 异步IO操作的控制块
struct aiocb aio_cb;// 信号处理函数:当异步IO完成时被调用
void aio_completion_handler(int signo, siginfo_t* info, void* context) {if (info->si_signo == SIGIO) {// 检查异步操作是否成功完成if (aio_error(&aio_cb) == 0) {// 获取实际读取的字节数ssize_t bytes_read = aio_return(&aio_cb);if (bytes_read > 0) {std::cout << "异步读取完成,读取了 " << bytes_read << " 字节: " << std::endl;std::cout << static_cast<char*>(aio_cb.aio_buf) << std::endl;} else if (bytes_read == 0) {std::cout << "已到达文件末尾" << std::endl;}} else {std::cerr << "异步读取失败: " << strerror(aio_error(&aio_cb)) << std::endl;}}
}int main(int argc, char* argv[]) {if (argc != 2) {std::cerr << "用法: " << argv[0] << " <文件名>" << std::endl;return 1;}const char* filename = argv[1];// 1. 打开文件(同步操作)int fd = open(filename, O_RDONLY);if (fd == -1) {std::cerr << "打开文件失败: " << strerror(errno) << std::endl;return 1;}// 2. 初始化异步IO控制块memset(&aio_cb, 0, sizeof(struct aiocb));// 分配缓冲区char* buffer = new char[BUFFER_SIZE];aio_cb.aio_buf = buffer;aio_cb.aio_nbytes = BUFFER_SIZE - 1;  // 留一个字节给终止符aio_cb.aio_fildes = fd;               // 文件描述符aio_cb.aio_offset = 0;                // 读取起始位置// 3. 设置信号处理:当异步IO完成时接收SIGIO信号struct sigaction sa;memset(&sa, 0, sizeof(struct sigaction));sa.sa_sigaction = aio_completion_handler;  // 信号处理函数sa.sa_flags = SA_SIGINFO;                  // 使用sigaction风格的处理函数if (sigaction(SIGIO, &sa, NULL) == -1) {std::cerr << "设置信号处理失败: " << strerror(errno) << std::endl;close(fd);delete[] buffer;return 1;}// 4. 设置文件描述符的所有者,让内核知道向哪个进程发送信号if (fcntl(fd, F_SETOWN, getpid()) == -1) {std::cerr << "设置文件所有者失败: " << strerror(errno) << std::endl;close(fd);delete[] buffer;return 1;}// 5. 启动异步读取操作if (aio_read(&aio_cb) == -1) {std::cerr << "启动异步读取失败: " << strerror(errno) << std::endl;close(fd);delete[] buffer;return 1;}std::cout << "异步读取已启动,主线程可以执行其他任务..." << std::endl;// 6. 主线程执行其他任务(模拟)for (int i = 0; i < 5; ++i) {std::cout << "主线程正在执行任务 " << i + 1 << std::endl;sleep(1);  // 模拟耗时操作}// 7. 清理资源close(fd);delete[] buffer;return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA核心基础篇-修饰符

Java 修饰符主要用于定义类、方法或变量&#xff0c;通常放在语句的最前端&#xff0c;可分为访问修饰符和非访问修饰符两类。一、访问修饰符public&#xff1a;对所有类可见&#xff0c;可用于类、接口、变量和方法。被声明为 public 的类、方法、构造方法和接口能够被任何其他…

笔试——Day46

文章目录第一题题目思路代码第二题题目思路代码第三题题目思路代码第一题 题目 AOE还是单体&#xff1f; 思路 贪心 剩余怪物数量 >x时&#xff0c;使用AOE&#xff1b;否则使用单体 代码 #include <iostream> #include <algorithm> using namespace std;…

零工合规挑战:盖雅以智能安全体系重构企业用工风控

国家税务总局发布的2025年第15号公告&#xff0c;将多种互联网平台企业纳入涉税信息报送范围&#xff0c;这让灵活用工平台的数据和网络安全问题成为行业关注的焦点。在海量零工信息和企业数据流转的过程中&#xff0c;数据泄露和网络攻击的风险不断上升&#xff0c;迫使平台在…

非线性规划学习笔记

非线性规划学习笔记 一、非线性规划的应用 非线性规划&#xff08;Nonlinear Programming, NLP&#xff09;在很多领域都有重要应用&#xff0c;主要包括&#xff1a; 工程设计优化&#xff1a;结构优化、电路参数优化、交通线路设计经济与管理&#xff1a;投资组合优化、生产计…

网络模型深度解析:CNI、Pod通信与NetworkPolicy

目录 专栏介绍 作者与平台 您将学到什么&#xff1f; 学习特色 网络模型深度解析&#xff1a;CNI、Pod通信与NetworkPolicy 第一部分&#xff1a;CNI 插件原理 - 网络基础设施的构建者 1.1 CNI 规范&#xff1a;标准化网络接入的基石 1.2 Flannel&#xff1a;简单高效的…

数据结构青铜到王者第二话---数据结构基本常识(2)

续接上一话 一、包装类 在Java中&#xff0c;由于基本类型不是继承自Object&#xff0c;为了在泛型代码中可以支持基本类型&#xff0c;Java给每个基本类型都对应了一个包装类型。 1、基本数据类型和对应的包装类 除了 Integer 和 Character&#xff0c; 其余基本类型的包装类…

fastdds qos:DeadlineQosPolicy

1含义DeadlineQosPolicy这种qos使用在DataWriter、DataReader、Topic。该qos用来监督数据是不是按照预期的频率进行收发。假如数据是周期性发送和接收&#xff0c;周期是固定的100ms&#xff0c;我们如果想要监督数据收发是不是按照预期的周期进行的&#xff0c;那么就可以配置…

QT-窗口类部件

Qt窗口类部件 一、窗口类部件 窗口就是没有父部件的部件&#xff0c;所以又称顶级部件。窗口类主要包括基本窗口类QWidget、对话框类QDialog和主窗口类QMainWindow三种。QObject是Qt框架中的一个核心基类&#xff0c;它提供了对象模型和信号槽机制。而QPaintDevice及其子类则提…

【CSP初赛】程序阅读3

文章目录前置知识阅读程序判断选择答案解析判断选择总结前置知识 埃氏筛素数、C 基础。 阅读程序 #include <bits/stdc.h> using namespace std; int main(){int a1[51] {0};int i,j,t,t2,n 50;for(i 2;i<sqrt(n);i){if(a1[i] 0){t2 n/i;for(j 2;j<t2;j) …

【ESP32-IDF】高级外设开发4:SPI

系列文章目录 持续更新中… 文章目录系列文章目录前言一、SPI概述1.主要功能2.SPI控制器架构3.SPI通信模式4.SPI数据帧与事务5.DMA与传输性能6.中断与驱动事件二、SPI类型定义及相关API三、SPI示例程序总结前言 在嵌入式开发中&#xff0c;SPI&#xff08;串行外设接口&#…

遥感机器学习入门实战教程|Sklearn案例⑧:评估指标(metrics)全解析

很多同学问&#xff1a;“模型好不好&#xff0c;怎么量化&#xff1f;” 本篇系统梳理 sklearn.metrics 中常用且“够用”的多分类指标&#xff0c;并给出一段可直接运行的示例代码&#xff0c;覆盖&#xff1a;准确率、宏/微/加权 F1、Kappa、MCC、混淆矩阵&#xff08;计数/…

【Bluedroid】深入解析A2DP SBC编码器初始化(a2dp_sbc_encoder_init)

SBC(Subband Coding)作为蓝牙 A2DP 协议的标准编解码器,其编码器的初始化与参数配置直接影响音频传输的音质、效率与兼容性。本文基于Andoird A2DP 协议栈源码,系统剖析 SBC 编码器的初始化流程,包括核心参数(比特池、采样率、声道模式等)的解析、计算与动态调整逻辑,以…

linux shell测试函数

在 C 语言中&#xff0c;int main(int argc, char *argv[])是程序的入口函数&#xff0c;而​​在 main函数中调用专门的测试逻辑&#xff08;如测试函数&#xff09;​​的程序结构&#xff0c;通常被称为​​测试程序&#xff08;Test Program&#xff09;​​或​​测试驱动…

【Java SE】抽象类、接口与Object类

文章目录一、 抽象类&#xff08;Abstract Class&#xff09;1.1 什么是抽象类&#xff1f;1.2 抽象类的语法1.2.1 定义抽象类1.2.2 继承抽象类1.3 抽象类的特性1.3.1 不能直接实例化1.3.2 抽象方法的限制1.3.3 抽象类可以包含构造方法1.3.4 抽象类不一定包含抽象方法1.3.5 抽象…

Autodl 创建新虚拟环境 python3.9

问题&#xff1a;本人在autodl上保存的环境因为很长时间没有开机&#xff0c;autodl竟然给我删除了。后来看了官网的介绍我才发现&#xff0c;原来15天不开机&#xff0c;autodl就会自动释放实例。 因此&#xff0c;我就自己重新选了一个虚拟环境&#xff0c;从头开始配置。 GP…

应急响应靶机-WindowsServer2022挖矿事件

依旧手痒开局&#xff0c;知攻善防实验室的原创靶机 https://mp.weixin.qq.com/s/URrNHvQSnFKOyefHKXKjQQ 相关账户密码&#xff1a; Administrator/zgsf123 注意&#xff1a;做个原始快照&#xff08;方便日后复习&#xff09;&#xff0c;安装VMware tool&#xff08;安装后图…

PCB电路设计学习3 电路原理图设计 元件PCB封装设计与添加

目录PCB电路设计学习3五、电路原理图设计5.1 32个发光二极管电路5.2 单片机外围电路5.3 供电与程序下载电路5.4 连接各部分网络&#xff0c;绘制边框和说明六、元件PCB封装设计与添加6.1 名词解释6.2 绘制PCB附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)PCB电路设计学习3 …

redis---常用数据类型及内部编码

Redis 中每种常用数据类型都对应多种内部编码&#xff0c;这些编码会根据数据特征&#xff08;如大小、数量&#xff09;自动切换&#xff0c;以平衡存储效率和操作性能。1.字符串&#xff08;String&#xff09;用途&#xff1a;存储文本、数字或二进制数据&#xff0c;是最基…

crypto.randomUUID is not a function

在本地运行时 crypto.randomUUID 好使&#xff0c;build 后放到服务器上用域名访问就不好使。原因&#xff1a;浏览器策略&#xff0c;浏览器在非https、localhost的环境中访问时&#xff0c;crypto.randomUUID 是不可用的开发时使用的是localhost正常访问 生产临时使用的是htt…

【思考】什么是服务器?什么是服务?什么是部署?

文章目录1 什么是服务器&#xff1f;什么是服务&#xff1f;端口是什么意思&#xff1f;2 什么是部署&#xff1f;1 什么是服务器&#xff1f;什么是服务&#xff1f;端口是什么意思&#xff1f; 服务器本质是一台运行着程序的电脑&#xff0c;它可以运行着很多程序&#xff0c…