计算机基础-IO

一、裸机中的IO

我们先看下计算机的组成部分:

从图中我们很清楚的看到Input/Output 即为 IO,也就是计算机主机和外部设备进行信息的交换

这种交换可能是磁盘IO也有可能是网络IO。

二、操作系统中的IO

操作系统分为内核态和用户态,且默认认为应用程序直接和磁盘或网卡进行IO操作是不安全的。因此操作系统为上层提供了相应的API供应用程序调用。操作系统接收到IO调用然后去内核进行IO执行。如图所示:

         

操作系统内核完成IO操作还包括两个过程:

准备数据阶段:内核等待I/O设备准备好数据

拷贝数据阶段:将数据从内核缓冲区copy到用户进程缓冲区

一次IO的本质其实就是: 等待数据 + 拷贝

三、IO分类

一个完整的IO过程包括以下几个步骤

1、用户应用程序向操作系统发起IO请求

2、操作系统向磁盘或网卡或者外部设备发送IO请求(用户应用程序等待1

3、操作系统等待磁盘或者网卡或者外部设备准备数据(用户应用程序等待2

4、操作系统将将数据加载到内核缓冲区(用户应用程序等待3

5、操作系统将数据copy到用户进程缓冲区(用户应用程序等待4

6、用户应用程序使用IO资源

1、阻塞IO

阻塞IO即用户程序发出IO操作调用后,等待内核返回数据,内核发起IO执行等待网卡或磁盘返回数据,一直等到数据具备,内核将数据copy到用户程序使用。期间用户程序都是阻塞状态,效率低,CPU利用率差。

问题:用户程序在等待数据传输时无法执行其他任务,‌导致资源利用率低下。利用多线程来提高并发也会导致调度成本的增加,也会增加编程的难度

2、同步非阻塞IO

用户应用程序向操作系统发起IO请求后,立刻返回一个标识(此时IO资源还不可用),用户应用程序可以先做其他的事情(比如再次去接收客户端的连接请求)。用轮询机制查询IO资源状态等到资源具备后,再进行数据copy进行使用。

问题:如果有1万个并发会导致用户程序需要发起1万次系统调用,增加了成本(基本开销有:用户栈、内核栈切换,保存寄存器信息,由于用户空间和内核空间地址不同会导致cpu缓存失效,也有可能导致页表切换,因此可能导致cpu缓存失效,一般需要几百ns,而普通的函数调用只需要1ns)

3、多路复用IO

提供一种机制,可以监听多个文件描述符,一旦发现有就绪状态就通知用户应用程序执行读写操作

select

POSIX标准中的

有文件描述符个数限制(1024),linux/posix_types.h头文件有这样的声明:

#define __FD_SETSIZE 1024 

比如有1千个并发,用户程序调用操作系统select函数,将1千个文件描述符传给内核区域,内核遍历这1千个文件描述符,如果没有就绪就阻塞当前进程,如果有数据具备的就给其打上标识返回给用户程序。用户程序遍历1千文件描述符,发现数据具备的文件描述符后进行读写操作(从linux帮助手册中可以看到select()还是有bug的,特殊情况下会将文件描述符阻塞状态当作非阻塞状态使用

epoll

Linux特有的

没有文件描述符个数限制,它的限制是操作系统限制的最大的打开文件句柄数目(用户可以自己设定)

用mmap在调用进程的虚拟地址空间中创建新的映射,让内核和用户应用程序都可以访问这份地址空间,并用红黑树存放所有的文件描述符,再用一个链表存放已经就绪的文件描述符,用户应用程序只需要关注这个链表就可以了

4、异步IO

可以看到非阻塞IO解决了步骤2-步骤4的等待问题,但是步骤5还是需要等待。异步IO可以做到步骤5完成后再通知用户应用程序,这样用户应用程序就可以直接使用了。

目前只有Windows上支持AIO,linux上还不支持,因此AIO的使用并没有NIO广泛。

四、相关linux操作系统调用

1、read

ssize_t read(int fd, void *buf, size_t count);

描述:

        传一个文件描述符、buf缓冲区。返回读取的字节数

        从文件描述符fd向buf缓冲区读取多个字节

        读取操作从当前文件偏移量开始,文件偏移量按读取的字节数递增,如果当前文件偏移位于或超过文件末尾,则不会读取任何字节,read()返回零。

2、write

ssize_t write(int fd, const void *buf, size_t count);

描述:

        传一个文件描述符、buf缓冲区。返回写的字节数

        从指向缓冲区的buf向文件描述符fd引用的文件写入计数字节

        如果底层物理介质上的空间不足,或者遇到RLIMIT_FSIZE资源限制可参考函数 setrlimit(2),

        如果在写入的字节数少于计数字节后,调用被信号处理程序中断,则写入的字节数量可能小于计数。可参考函数pipe(7)

        对于可查找的文件可以调用lseek(2)

注意:

        write()的成功返回并不能保证数据已提交到磁盘。事实上,在一些有缺陷的实现中,它甚至不能保证已成功为数据保留了空间。唯一可以确定的方法是在写入所有数据后调用fsync(2)

        如果write()在写入任何字节之前被信号处理程序中断,则调用失败,并返回错误EINTR;如果在写入至少一个字节后中断,则调用成功,并返回写入的字节数。(所以上层必须根据返回的字节数判断文件是否写完了)

3、select

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

void FD_CLR(int fd, fd_set *set);

int  FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);

描述:

        select() 和pselect() 允许程序监视多个文件描述符,等待一个或多个文件描述符“ready”进行某类I/O操作(例如,可能输入)。如果可以在不阻塞的情况下执行相应的I/O操作(例如read(2)),则认为文件描述符已准备就绪。

         select() 和pselect() 的操作是相同的,但有以下三个区别:

        1、select() 使用一个超时,它是一个结构体timeval(带秒和微秒),而pselect() 使用结构体timespec(含秒和纳秒)。

        2、select() 可以更新timeout参数,以指示还剩多少时间。pselect() 不会更改此参数。

        3、select() 没有sigmask参数,其行为类似于使用NULL sigmask调用的pselect()。

        提供了四个宏来操纵集合。FD_ZERO() 清除集合。FD_SET()和FD_CLR()分别在集合中添加和删除给定的文件描述符。FD_ISSET()测试文件描述符是否是集合的一部分;这在select()返回后很有用。

BUGS:

        从2.1版本开始,glibc提供了使用sigprocmask(2) 和select()  实现的pselect() 的仿真。此实现仍然容易受到pselect() 旨在防止的竞争条件的影响。glibc的现代版本在提供它的内核上使用(无种族)pselect() 系统调用

        在缺少pselect() 的系统上,可以使用自管道技巧实现可靠(且更便携)的信号捕获。在这种技术中,信号处理程序将一个字节写入管道,管道的另一端由主程序中的select()监视。(为了避免在写入可能已满的管道或从可能为空的管道读取时可能发生阻塞,在读取和写入管道时使用非阻塞I/O。)

        在Linux下,select()可能会将套接字文件描述符报告为“准备读取”,但随后的读取仍会阻塞。例如,当数据已经到达,但在检查时具有错误的校验和并被丢弃时,可能会发生这种情况。可能还有其他情况,其中文件描述符被虚假地报告为就绪。因此,在不应该阻塞的套接字上使用O_NONBLOCK可能更安全。

        在Linux上,如果调用被信号处理程序中断(即EINTR错误返回),select()也会修改超时。POSIX.1-2001不允许这样做。Linux pselect()系统调用具有相同的行为,但glibc包装器通过在内部将超时复制到本地变量并将该变量传递给系统调用来隐藏此行为

4、epoll

epoll - I/O event notification facility (epoll不是一个函数调用,而是一个IO事件通知工具)

描述:

        epoll API执行与poll(2)类似的任务:监视多个文件描述符以查看是否可以对其中任何一个进行I/O。epoll API既可以用作边缘触发接口,也可以用作级别触发接口,并且可以很好地扩展到大量关注的文件描述符。提供以下系统调用来创建和管理epoll实例

        epoll_create(2) : 创建一个epoll实例,并返回一个引用该实例的文件描述符

        然后通过epoll_ctl(2) 注册、修改、删除特定文件描述符。当前在epoll实例上注册的文件描述符集有时称为epoll集

        epoll_wait(2)等待I/O事件,如果当前没有可用的事件,则阻塞调用线程

        epoll有两种工作模式ET(边缘触发)、LT(水平触发)(默认使用LT)

        1、表示管道读取端(rfd)的文件描述符在epoll实例上注册。

        2、管道写入器在管道的写入侧写入2kB的数据。

        3、调用epoll_wait(2) 将返回rfd作为就绪文件描述符。

        4、管道读取器从rfd读取1kB的数据。

        5、对epoll_wait(2) 的调用已完成。

        如果已使用EPOLLET ET模式,标志将rfd文件描述符添加到epoll接口,则步骤5中对epoll_wait(2)的调用可能会挂起,尽管文件输入缓冲区中仍存在可用数据;同时,远程对等体可能期望基于其已经发送的数据的响应。这样做的原因是,边缘触发模式仅在受监视的文件描述符发生更改时才传递事件。因此,在步骤5中,调用者可能会等待输入缓冲区中已经存在的一些数据。在上面的例子中,由于在2中完成了写入,将在rfd上生成一个事件,该事件在3中被消耗。由于4中完成的读取操作不会消耗整个缓冲区数据,因此步骤5中对epoll_wait(2)的调用可能会无限期阻塞。

5、mmap

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

int munmap(void *addr, size_t length);

描述:

        将文件或设备映射或取消映射到内存中

        mmap() 在调用进程的虚拟地址空间中创建一个新的映射。新映射的起始地址在addr中指定。length参数指定映射的长度。

        如果addr为NULL,则内核选择创建映射的地址;这是创建新映射的最便携的方法。如果addr不为NULL,则内核将其视为关于映射位置的提示;在Linux上,映射将在附近的页面边界处创建。新映射的地址作为调用的结果返回。

        文件映射的内容使用从文件描述符fd引用的文件(或其他对象)中的偏移量开始的长度字节进行初始化。偏移量必须是sysconf(_SC_page_size)返回的页面大小的倍数。

        prot参数描述了映射所需的内存保护(并且不得与文件的打开模式冲突)。它要么是PROT_NONE,要么是以下一个或多个标志的位(PROT_EXEC可执行、PROT_READ可读取、PROT_WRITE可写入、PROT_NONE无法访问)

        flags参数确定映射的更新是否对映射同一区域的其他进程可见,以及更新是否会传递到底层文件。此行为是通过在标志中包含以下值之一来确定的:(MAP_SHARED、MAP_SHARED_VALIDATE等等)

        mmap() 映射的内存在fork(2) 中保留,具有相同的属性。

        文件以页面大小的倍数映射。对于不是页面大小倍数的文件,映射时剩余内存为零,对该区域的写入不会写入文件。未指定更改映射基础文件大小对与文件添加或删除区域对应的页面的影响

        munmap()

        munmap() 系统调用删除指定地址范围的映射,并导致对该范围内地址的进一步引用生成无效的内存引用。进程终止时,该区域也会自动取消映射。另一方面,关闭文件描述符并不会取消映射该区域。

注意:

        不同的硬件PROT_WRITE、PROT_READ、PROT_EXEC状态可能表示方式不同

BUGS:

        在Linux上,没有像上面MAP_NORESERVE下建议的那样的保证。默认情况下,当系统内存不足时,任何进程都可以随时被终止。

        在2.6.7之前的内核中,只有当prot指定为prot_NONE时,MAP_POPULATE标志才有效。

        指定系统应始终对对象末尾的任何部分页面进行零填充,并且系统永远不会在对象末尾之后写入对对象的任何修改。在Linux上,当您在对象结束后将数据写入此类部分页面时,即使在文件关闭并取消映射后,数据也会留在页面缓存中,即使数据从未写入文件本身,后续映射也可能会看到修改后的内容。在某些情况下,这可以通过在取消映射之前调用msync(2) 来修复;然而,这在tmpfs上不起作用(例如,在使用shm_overview(7)中记录的POSIX共享内存接口时)。

五、代码示例

1、BIO

Client


import java.io.*;
import java.net.Socket;public class BIOClient {public static void main(String[] args) throws Exception {//构建客户端Socket,并于服务端建立连接Socket socket = new Socket("127.0.0.1", 8888);//构建输出流向服务端发送信息OutputStream outputStream = socket.getOutputStream();PrintStream ps = new PrintStream(outputStream);ps.println("hello");ps.flush();//构建输入流获取客户端的信息InputStream inputStream = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));String s = null;//阻塞等待服务端发送给客户端的数据while ((s = br.readLine()) != null) {System.out.println(Thread.currentThread().getName()+" 接收到服务端的数据:" + s);}outputStream.close();socket.close();}
}

Server


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class BIOServer {public static void main(String[] args) throws Exception {int port = 8888; // 服务器端口ServerSocket serverSocket = new ServerSocket(port);while (true) {// 阻塞等待客户端连接Socket socket = serverSocket.accept();System.out.println("客户端连接成功:" + socket.getRemoteSocketAddress());// 每个连接创建一个线程处理 ,且每个线程同一时刻只能处理一个连接new Thread(() -> {try {// 输入流读取数据 输入流是客户端发的信息,输出流是自己发出去给客户端的信息BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));String inputLine;OutputStream outputStream = socket.getOutputStream();PrintWriter printWriter = new PrintWriter(outputStream);//阻塞状态,等待输入信息完全接收才给客户端发送信息while ((inputLine = in.readLine()) != null) {printWriter.println("给客户端的消息:收到你的消息:"+inputLine);printWriter.flush();System.out.println("客户端消息:" + inputLine);}printWriter.close();in.close();} catch (Exception e) {e.printStackTrace();}}).start();}}
}

2、NIO

Client


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class NIOClient {public static void main(String[] args) {try {// 创建 SocketChannelSocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("localhost", 8080));// 发送数据到服务器String message = "Hello, Server!";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());socketChannel.write(buffer);socketChannel.close();  // 关闭连接} catch (IOException e) {e.printStackTrace();}}
}

Server


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;public class NIOServer {public static void main(String[] args) {try {// 创建 ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(8080));while (true) {// 等待客户端连接SocketChannel clientSocketChannel = serverSocketChannel.accept();System.out.println("Client connected: " + clientSocketChannel.getRemoteAddress());// 创建 ByteBuffer 用于读取数据ByteBuffer buffer = ByteBuffer.allocate(1024);// 从客户端读取数据int bytesRead;while ((bytesRead = clientSocketChannel.read(buffer)) != -1) {buffer.flip();  // 切换至读模式while (buffer.hasRemaining()) {System.out.print((char) buffer.get());  // 打印接收到的数据}buffer.clear();  // 清空缓冲区,准备下一次读取}clientSocketChannel.close();  // 关闭客户端连接}} catch (IOException e) {e.printStackTrace();}}
}

3、NIO epoll

Client


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;public class NioEpollClient {private final Selector selector;private final SocketChannel socketChannel;public NioEpollClient(String host, int port) throws IOException {socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);// 连接到服务器if (!socketChannel.connect(new InetSocketAddress(host, port))) {while (!socketChannel.finishConnect()) {// 等待连接完成,可以在这里添加超时逻辑}}// 注册连接完成事件selector = Selector.open();socketChannel.register(selector, SelectionKey.OP_CONNECT);}public void send(String message) throws IOException {byte[] data = message.getBytes();socketChannel.write(java.nio.ByteBuffer.wrap(data));}public void close() throws IOException {socketChannel.close();selector.close();}public static void main(String[] args) throws IOException {NioEpollClient client = new NioEpollClient("localhost", 8888);client.send("Hello Server");client.close();}

Server


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;public class NioEpollServer {private Selector selector;private ServerSocketChannel serverSocket;public NioEpollServer(int port) throws IOException {selector = Selector.open();serverSocket = ServerSocketChannel.open();serverSocket.configureBlocking(false);serverSocket.socket().bind(new InetSocketAddress(port));serverSocket.register(selector, SelectionKey.OP_ACCEPT);}public void listen() throws IOException {while (true) {selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();while (it.hasNext()) {SelectionKey key = it.next();it.remove();if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel socket = ssc.accept();socket.configureBlocking(false);socket.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {SocketChannel socket = (SocketChannel) key.channel();int count;StringBuilder buffer = new StringBuilder();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);while ((count = socket.read(byteBuffer)) > 0) {byteBuffer.flip();buffer.append(Charset.forName("UTF-8").decode(byteBuffer));byteBuffer.clear();}// Handle received dataif (buffer.length() > 0) {System.out.println("Received data: " + buffer.toString());}// Prepare responseString response = "Hello Client!";ByteBuffer outBuffer = ByteBuffer.wrap(response.getBytes());socket.write(outBuffer);}else if(key.isWritable()){SocketChannel socket = (SocketChannel) key.channel();String response = "Hello Client 2 !";ByteBuffer outBuffer = ByteBuffer.wrap(response.getBytes());socket.write(outBuffer);}}}}public static void main(String[] args) throws IOException {NioEpollServer server = new NioEpollServer(8888);server.listen();}
}

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

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

相关文章

活动预告|8月3日 Streaming Lakehouse Meetup · Online 与你相约!

随着大数据分析技术的发展&#xff0c;越来越多的企业采用了数据湖架构。基于 Lakehouse 的架构优势&#xff0c;结合 Flink 的 Streaming 实时流处理能力&#xff0c;Flink 推出了新一代的“Streaming Lakehouse”技术。这一技术旨在能够让数据在湖上自由流动&#xff0c;为用…

js 习题 3

文章目录 绪论12345678910 求最长公共后缀111213 最大公约数1415结语 绪论 『虽有遗憾&#xff0c;绝不后悔。』—— 「古剑奇谭」 1 let buf"";process.stdin.on("readable",function(){let chunkprocess.stdin.read();if(chunk){bufchunk.toString();} …

从零开始创建vue3项目——包含项目初始化、element-plus、eslint、axios、router、pinia、echarts

项目启动 初始化vue3项目 这里建议先下载pnpm&#xff0c;下载速度更快&#xff0c;如果还没下载可以使用 npm install -g pnpm 如果遇到报错问题&#xff0c;如下 可以在命令行输入下面的指令以切换到淘宝镜像源 npm config set registry https://registry.npm.taobao.org…

2024最佳游戏引擎排行

游戏产业几十年来一直是一个大生意&#xff0c;而且发展势头迅猛。据估计&#xff0c;全球游戏市场在 2025 年每年将达到 5031.4 亿美元&#xff0c;2023 年为 3960 亿美元。 尽管如今有市面上有各种各样的解决方案&#xff0c;但游戏开发人员和管理者在选择适合他们需求的游戏…

kettle从入门到精通 第八十课 ETL之kettle kettle中的json对象字段写入postgresql中的json字段

场景&#xff1a;源数据库表为mysql的其中有json字段&#xff0c;通过kettle 查询出来 插入到目标数据库 postgresql中&#xff0c;对应的表中也有json字段。。但是报错&#xff0c;提示kettle查询出来是varchar的的字段&#xff0c;无法插入到目标数据库中。 1、创建测试表。 …

golang设置远程调试

1. 目标机器构建安装dlv https://github.com/go-delve/delve go build之后将编译号的dlv命令路径添加到PATH里 2. 目标机器下载源代码并且运行dlv dlv debug --headless --listen:2345 --api-version2 --accept-multiclient 3.本机添加go remote 4. 设置断点即可

Cesium高性能渲染海量矢量建筑

0、数据输入为类似Geojson的压缩文件和纹理图片&#xff0c;基于DrawCommand命令绘制&#xff1b; 1、自定义建筑几何&#xff0c;包括顶点、法线、纹理等&#xff1b; 2、自定义纹理贴图&#xff0c;包括按建筑高度贴图、mipmap多级纹理&#xff1b; 3、自定义批处理表&…

DVWA中命令执行漏洞细说

在攻击中&#xff0c;命令注入是比较常见的方式&#xff0c;今天我们细说在软件开发中如何避免命令执行漏洞 我们通过DVWA中不同的安全等级来细说命令执行漏洞 1、先调整DVWA的安全等级为Lower,调整等级在DVWA Security页面调整 2、在Command Injection页面输入127.0.0.1&…

AI在Facebook的应用:预见智能化社交的新前景

在数字化时代&#xff0c;社交媒体平台已成为我们生活的重要组成部分&#xff0c;而人工智能&#xff08;AI&#xff09;的快速发展正推动着这些平台向更智能、更个性化的方向发展。Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;正不断探索和应用AI技术&…

mysql面试(七)

前言 本章节列出了mysql在增删改查的时候&#xff0c;分别会涉及到哪些锁类型&#xff0c;又是如何交互的。 这个章节也是mysql面试基础系列的最后一章&#xff0c;后面准备更新redis数据类型和分布式锁相关问题。如果各位看官有什么问题的话&#xff0c;可以留言。 锁 之前…

【MySQL进阶之路 | 高级篇】MVCC三剑客:隐藏字段,Undo Log,ReadView

1. 再谈隔离级别 我们知道事务有四个隔离级别&#xff0c;可能存在三种并发问题&#xff1a; 在MySQL中&#xff0c;默认的隔离级别是可重复读&#xff0c;可以解决脏读和不可重复读的问题&#xff0c;如果仅从定义的角度来看&#xff0c;它并不能解决幻读问题。如果我们想要解…

2024年jupyter notebook如何复制单元格cell输出的图片

背景 之前使用jupyter notebook复制图片一直是正常&#xff0c;右键就行&#xff0c;可以找到复制图片或者另存为的选项。 但是最近重新安装了anaconda&#xff0c;发现jupyter notebook升级了&#xff0c;和原来的界面不一样了。 如果有一个图片&#xff0c;我们右键&#x…

【Linux】—管理、设置防火墙规则(firewalld详解)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

[OJ]平均串问题,存在超时问题未解决

众所周知&#xff0c;两个数a和b的平均数计算公式为(ab)/2。 实际上平均数也可以描述为&#xff1a;从较小的数依次遍历到较大的数&#xff0c;将遍历的数放入一个列表中&#xff0c;该列表的中心元素。例如&#xff1a;求 3和7的平均数&#xff0c;列表为{3,4,5,6,7}&#xff…

2024-06学习笔记

1.事务与数据库链接的占用 如果用Transactional注解&#xff0c;那在第一次与数据库交互的时候&#xff0c;就会打开数据库链接&#xff0c;再整个方法执行完&#xff0c;才会关闭数据库链接。 即使后边用的事务传播是required_new,那之前的事务也是被挂起&#xff0c;不会被…

静态路由学习笔记

1. 静态路由应用场景 &#xff08;1&#xff09;静态路由由网络管理员手动配置&#xff0c;配置方便&#xff0c;对系统要求低&#xff0c;适用于拓扑结构简单并且稳定的小型网络。 &#xff08;2&#xff09;缺点是不能自动适应网络拓扑的变化&#xff0c;需要人工干预过多。…

day05 Router、vuex、axios

配置 router和vuex需要在创建vue项目的时候&#xff0c;开始的时候选择Manually select features&#xff0c;于是就可以在下一个创建配置讯问中选择router和vuex。 axios则需要执行命令行&#xff1a; npm install axios -S 之后再在需要发送请求的view导入即可。 router…

研发(RD)注意事项 / 复杂项目规划、控制方法 PERT 和 CPM

注&#xff1a;机翻&#xff0c;未校对&#xff0c;去掉了原文中广告。 What Is Research and Development (R&D)? 什么是研发&#xff08;R&D&#xff09;&#xff1f; Investopedia / Ellen Lindner Research and Development An ongoing effort to develop or impr…

springboot中使用knife4j访问接口文档的一系列问题

springboot中使用knife4j访问接口文档的一系列问题 1.个人介绍 &#x1f389;&#x1f389;&#x1f389;欢迎来到我的博客,我是一名自学了2年半前端的大一学生,熟悉的技术是JavaScript与Vue.目前正在往全栈方向前进, 如果我的博客给您带来了帮助欢迎您关注我,我将会持续不断的…