Java网络编程原理与实践--从Socket到BIO再到NIO

文章目录

  • Java网络编程原理与实践--从Socket到BIO再到NIO
    • Socket基本架构
    • Socket 基本使用
      • 简单一次发送接收
        • 客户端
        • 服务端
      • 字节流方式简单发送接收
        • 客户端
        • 服务端
      • 双向通信
        • 客户端
        • 服务端
      • 多次接收消息
        • 客户端
        • 服务端
    • Socket写法的问题
    • BIO
      • 简单流程
      • BIO写法
        • 客户端
        • 服务端
      • BIO的问题
    • NIO
      • 简述
      • Buffer
      • Channel(通道)
      • Selector(选择器)
        • 基本介绍
        • 使用实例

Java网络编程原理与实践–从Socket到BIO再到NIO

Socket基本架构

图来源:https://zhuanlan.zhihu.com/p/462497498
既然是网络的东西肯定得放个网络架构图,这张图不多说,感兴趣可以去链接详细看一下
在这里插入图片描述

Socket 基本使用

转自:https://blog.csdn.net/a78270528/article/details/80318571

简单一次发送接收

客户端
package Scoket.client;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;/**** 字符流方式*/
public class Client {public static void main(String[] args) {try {// 服务器的主机和端口String serverHost = "127.0.0.1";int serverPort = 6443;// 创建Socket对象,连接到服务器Socket socket = new Socket(serverHost, serverPort);// 获取输入流和输出流BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter output = new PrintWriter(socket.getOutputStream(), true);// 发送数据给服务器String messageToSend = "Hello, Server!";output.println(messageToSend);// 接收服务器的响应数据String dataReceived = input.readLine();System.out.println("Received from server: " + dataReceived);// 关闭连接socket.close();} catch (Exception e) {e.printStackTrace();}}
}
服务端
package Scoket.client;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;public class Server {public static void main(String[] args) {try {// 本地主机和端口String serverHost = "127.0.0.1";int serverPort = 6443;// 创建ServerSocket对象,绑定地址和端口ServerSocket serverSocket = new ServerSocket(serverPort);System.out.println("Server listening on " + serverHost + ":" + serverPort);// 接受客户端连接Socket clientSocket = serverSocket.accept();System.out.println("Accepted connection from " + clientSocket.getInetAddress());// 获取输入流和输出流BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);// 接收客户端发送的数据String dataReceived = input.readLine();System.out.println("Received from client: " + dataReceived);// 发送响应给客户端String messageToSend = "Hello, Client!";output.println(messageToSend);// 关闭连接clientSocket.close();serverSocket.close();} catch (Exception e) {e.printStackTrace();}}
}

如果进行debug会发现,服务端代码总共卡主两次:
1、 Socket clientSocket = serverSocket.accept(); 这里会监听端口,等待客户端请求建立连接,实际上是进行三次握手
2、 String dataReceived = input.readLine(); 这里是等待客户端发送数据,接收到数据会进行下一步
这两步骤需要注意,因为这是后面BIO和NIO的优化点

字节流方式简单发送接收

使用字节流处理,这可能使得处理字符串数据稍显繁琐。如果你的通信数据是文本,可能使用字符流更为方便。
但是数据更可控一些,下面简单罗列

客户端
package Scoket.client;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;/*** 字节流方式*/
public class Client1 {public static void main(String[] args) {try {String host = "127.0.0.1";int port = 6443;Socket socket = new Socket(host, port);OutputStream outputStream = socket.getOutputStream();String message = "message, 你好";socket.getOutputStream().write(message.getBytes(StandardCharsets.UTF_8));outputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
服务端
package Scoket.client;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Server1 {public static void main(String[] args) {try {int port = 6443;ServerSocket serverSocket = new ServerSocket(port);System.out.println("等待连接");Socket accept = serverSocket.accept();System.out.println("完成连接,等待传输数据");InputStream inputStream = accept.getInputStream();byte[] bytes = new byte[1024];int len;StringBuilder sb = new StringBuilder();while ((len = inputStream.read(bytes)) != -1){sb.append(new String(bytes, 0, len, "UTF-8"));}System.out.println("get message:" + sb);inputStream.close();accept.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}

双向通信

客户端
package Scoket.client;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;public class Client2 {public static void main(String[] args) {try {String host = "127.0.0.1";int port = 8443;Socket socket = new Socket(host, port);OutputStream outputStream = socket.getOutputStream();outputStream.write("我是客户,接受一下我的消息".getBytes(StandardCharsets.UTF_8));socket.shutdownOutput();InputStream inputStream = socket.getInputStream();byte[] bytes = new byte[1024];int len;StringBuilder stringBuilder = new StringBuilder();while ((len = inputStream.read(bytes)) != -1){stringBuilder.append(new String(bytes, 0, len, "UTF-8"));}System.out.println("get message:" + stringBuilder);inputStream.close();outputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
服务端
package Scoket.client;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;public class Server2 {public static void main(String[] args) {try {int port = 8443;ServerSocket serverSocket = new ServerSocket(port);Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();byte[] bytes = new byte[1024];int len ;StringBuilder sb = new StringBuilder();while ((len = inputStream.read(bytes)) != -1){sb.append(new String(bytes, 0, len, "UTF-8"));}System.out.println("server2 get Message:" + sb);OutputStream outputStream = socket.getOutputStream();outputStream.write("我是服务器".getBytes(StandardCharsets.UTF_8));inputStream.close();outputStream.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}

多次接收消息

客户端
package Scoket.client;import java.io.OutputStream;
import java.net.Socket;public class Client3 {public static void main(String args[]) throws Exception {// 要连接的服务端IP地址和端口String host = "127.0.0.1";int port = 8444;// 与服务端建立连接Socket socket = new Socket(host, port);// 建立连接后获得输出流OutputStream outputStream = socket.getOutputStream();String message = "你好  yiwangzhibujian";//首先需要计算得知消息的长度byte[] sendBytes = message.getBytes("UTF-8");//然后将消息的长度优先发送出去outputStream.write(sendBytes.length >>8);outputStream.write(sendBytes.length);//然后将消息再次发送出去outputStream.write(sendBytes);outputStream.flush();//==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法message = "第二条消息";sendBytes = message.getBytes("UTF-8");outputStream.write(sendBytes.length >>8);outputStream.write(sendBytes.length);outputStream.write(sendBytes);outputStream.flush();//==========此处重复发送一次,实际项目中为多个命名,此处只为展示用法message = "the third message!";sendBytes = message.getBytes("UTF-8");outputStream.write(sendBytes.length >>8);outputStream.write(sendBytes.length);outputStream.write(sendBytes);    outputStream.close();socket.close();}
}
服务端
package Scoket.client;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class Server3 {public static void main(String[] args) {try {int port = 8444;ServerSocket serverSocket = new ServerSocket(port);Socket accept = serverSocket.accept();InputStream inputStream = accept.getInputStream();byte[] bytes;while (true){int first = inputStream.read();if(first == -1){break;}int second = inputStream.read();int len = (first << 8) +second;bytes = new byte[len];inputStream.read(bytes);System.out.println("Server3 get message:" + new String(bytes, "UTF-8"));}} catch (IOException e) {e.printStackTrace();}}
}

Socket写法的问题

上面的代码有些很大的问题
1、阻塞式 I/O: 这是最大的缺点之一。在 accept()、readLine() 等方法调用时,程序会被阻塞,等待客户端连接或数据到来。这可能导致服务器在处理多个客户端时性能下降。
2、单线程处理: 服务器采用单线程处理客户端连接。这意味着一次只能处理一个客户端连接,如果有大量的客户端同时连接,性能会受到影响。
3、不适用于高并发: 由于采用单线程处理方式,不适合高并发环境。在高并发情况下,建议考虑使用多线程或异步 I/O 模型。
4、异常处理不足: 缺少一些异常处理,例如,在 accept()、readLine() 中可能会抛出异常,而在示例中并未捕获和处理这些异常。
针对1、2可以采用BIO方式
针对1、2、3可以采用NIO
接下来将会优化代码分别介绍BIO和NIO

BIO

简单流程

服务器启动一个ServerSocket。
客户端启动一个Socket对服务器进行通信,默认情况下,服务器端需要对每一个客户端建立一个线程与之通信。
客户端发出请求后,先咨询服务器是否有线程相应,如果没有则会等待,或者被拒绝。
如果有响应,客户端线程会等待请求结束后,再继续执行。

BIO写法

客户端
package Scoket.client;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;public class BIOClient{public static void main(String[] args) {try {Socket socket = new Socket("127.0.0.1", 6666);OutputStream outputStream = socket.getOutputStream();outputStream.write("hi, i am client".getBytes(StandardCharsets.UTF_8));outputStream.flush();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
服务端

转自:https://juejin.cn/post/6924670437867651080

package Scoket.client;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BIOServer {public static void main(String[] args) throws IOException {// 创建线程池ExecutorService executorService = Executors.newCachedThreadPool();// 创建ServerSocket并且监听6666端口ServerSocket serverSocket = new ServerSocket(6666);while (true) {// 监听---一直等待客户端连接Socket socket = serverSocket.accept();// 连接来了之后,启用一个线程去执行里面的方法executorService.execute(() -> {try {// 获取客户端发送过来的输入流InputStream inputStream = socket.getInputStream();byte[] bytes = new byte[1024];int read = inputStream.read(bytes);// 读取发送过来的信息并打印if (read != -1) {System.out.println(new String(bytes, 0, read));}} catch (IOException e) {e.printStackTrace();} finally {// 断开通讯try {socket.close();} catch (IOException e) {e.printStackTrace();}}});}}
}

BIO的问题

上述写法主要是在服务端接受到一个客户端连接时,就开启一个线程,然后新建一个连接专门处理这个服务
可以看下accept代码

    public Socket accept() throws IOException {if (this.isClosed()) {throw new SocketException("Socket is closed");} else if (!this.isBound()) {throw new SocketException("Socket is not bound yet");} else {Socket s = new Socket((SocketImpl)null);this.implAccept(s);return s;}}

可以看到,每次accept就会新建一个Socket
因此会有如下问题:
每个请求都需要创建独立的线程,与对应的客户端进行数据读,业务处理,然后再数据写。
当并发数较大时,需要创建大量的线程来处理连接,系统资源占用较大。
连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在读操作上,造成线程资源浪费。

基于上面的问题产生了NIO

NIO

简述

Java NIO全称java non-blocking IO,是指JDK提供的新API。从JDK1.4开始,提供了一系列改进的输入/输出的新特性,被统称为NIO(所以也可称为New IO),是同步非阻塞的。
NIO相关类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写。
NIO有三大核心部分:
Channel(通道)
Buffer(缓冲区)
Selector(选择器)
NIO是面向缓冲区的。数据读取到一个它的稍后处理的缓冲区,需要时可以在缓冲区中前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
Java NIO的非阻塞模式,是一个线程从某通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个。
HTTP2.0使用了多路复用的技术,做到了同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
在这里插入图片描述

Buffer

Buffer(缓冲区):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由Buffer。
在使用Buffer进行数据读写的时候,主要是通过底层的这个数组来储存数据,但是具体的控制数据读写,是通过父类Buffer中的以下参数来控制的:

属性描述
Capacity容量,即可以容纳的最大数据量。在缓冲区被创建时被确定并且不能改变
Limit示缓冲区的当前终点,不能对缓冲区超过limit的位置进行读写操作,且limit是可以修改的
Position位置,下一个要被读/写的元素的索引,每次读写缓冲区数据时都会改变position的值,为下次读写做准备
Mark标记

一共有7个类直接继承了Buffer类,这7个子类分别是除了boolean外的其他7中数据类型的Buffer类。
在这七个子类中,都有一个相应数据类型的数组,比如IntBuffer中就有一个int类型的数组:
final int[] hb;
在ByteBuffer类中就有一个byte类型的数组:
final byte[] hb;
实例:

package Scoket.client;import java.nio.IntBuffer;public class Buffer {public static void main(String[] args) {// 创建一个IntBuffer对象实例,分配容量为5IntBuffer buffer = IntBuffer.allocate(5);for (int i = 0; i < buffer.capacity(); i++) {// 每次循环为buffer塞一个int类型的数值,经过5次循环后,buffer中应该有0、2、4、6、8这5个数buffer.put(i * 2);}// 当要将buffer从写入转换到读取的时候,需要调用flip()方法// flip()方法是将limit指向position的位置,并且再将position置0// 表示从头再读到调用flip()方法的地方buffer.flip();// hasRemaining()方法表示是否还有剩余的元素可读取// 里面是通过position < limit判断是否有剩余的元素while (buffer.hasRemaining()) {System.out.println(buffer.get());}// 这时将position的位置设置成1,limit的位置设置成4buffer.position(1);buffer.limit(4);// 因为不能读取超过limit的元素,并且从position位置开始读取,所以这里将会输出2、4、6while (buffer.hasRemaining()) {System.out.println(buffer.get());}}
}

Channel(通道)

NIO的通道类似于流,但两者之间有所区别:
通道可以同时进行读写,而流只能读或者只能写
通道可以实现异步读写数据
通道可以从缓冲区读取数据,也可以写数据到缓冲区
BIO的stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO中的通道(Channel)是双向的,可以读操作,也可以写操作。
Channel在NIO中是一个接口。
常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel、SocketChannel。FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写。

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class Channel {public static void main(String[] args) throws Exception {// 从桌面上随机取一张图片进行复制操作// 获取原图片和被复制图片路径的流FileInputStream fileInputStream = new FileInputStream("/src/main/resources/img.png");FileOutputStream fileOutputStream = new FileOutputStream("/src/main/resources/img_1.png");// 通过流的getChannel()方法获取两个通道FileChannel fileInputStreamChannel = fileInputStream.getChannel();FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();// 创建一个字节类型的缓冲区,并为其分配1024长度ByteBuffer byteBuffer = ByteBuffer.allocate(1024);// 每次读取图片的字节到缓冲区,当读返回-1时,表示读完了while (fileInputStreamChannel.read(byteBuffer) > -1) {// 调用flip()方法,从读的状态变为写的状态byteBuffer.flip();// 复制,将缓冲区中的数据写入到管道中fileOutputStreamChannel.write(byteBuffer);// 将缓冲区清空,以便于下一次读取byteBuffer.clear();}// 关闭Closeable对象fileOutputStreamChannel.close();fileInputStreamChannel.close();fileOutputStream.close();fileInputStream.close();}
}

Selector(选择器)

基本介绍

Java的NIO,用非阻塞的IO方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)。
Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取时间然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
只有在连接通道真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每一个连接都创建一个线程,不用去维护多个线程。避免了多个线程之间的上下文切换导致的开销
SelectionKey为Selector中,有一个Channel注册了,就会生成一个SelectionKey对象,在同步非阻塞中,Selector可以通过SelectionKey找到相应的Channel并处理。
SelectionKey在Selector和Channel的注册关系中一共分为四种:

Int OP_ACCEPT:有新的网络连接可以accept,值为16(1<<4)
int OP_CONNECT:代表连接已经建立,值为8(1<<3)
int OP_WRITE:代表写操作,值为4(1<<2)
int OP_READ:代表读操作,值为1(1<<0)

使用实例

客户端:

package Scoket.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) throws IOException {// 连接服务器SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("localhost", 6443));// 发送数据String message = "Hello, Server!";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes("UTF-8"));socketChannel.write(buffer);System.out.println("Sent to server: " + message);// 关闭连接socketChannel.close();}
}

服务端

package Scoket.client;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.util.Iterator;
import java.util.Set;public class NioServer {public static void main(String[] args) throws IOException {// 打开 SelectorSelector selector = Selector.open();// 打开 ServerSocketChannel,监听客户端连接ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(6443));// 设置为非阻塞模式serverSocketChannel.configureBlocking(false);// 注册接受连接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server listening on port 6443");while (true) {// 阻塞直到有就绪事件发生int readyChannels = selector.select();if (readyChannels == 0) {continue;}// 获取就绪事件的 SelectionKey 集合Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {// 有新的连接handleAccept(key, selector);} else if (key.isReadable()) {// 有数据可读handleRead(key);}keyIterator.remove();}}}private static void handleAccept(SelectionKey key, Selector selector) throws IOException {ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false);// 注册读事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("Accepted connection from: " + socketChannel.getRemoteAddress());}private static void handleRead(SelectionKey key) throws IOException {SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[bytesRead];buffer.get(data);System.out.println("Received from client: " + new String(data, "UTF-8"));// 在这里可以添加业务逻辑,然后将响应数据写入到 SocketChannel// ...// 关闭连接socketChannel.close();}}
}

参考源:
https://zhuanlan.zhihu.com/p/462497498
https://blog.csdn.net/a78270528/article/details/80318571
https://juejin.cn/post/6924670437867651080
https://juejin.cn/post/6925046428213608456

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

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

相关文章

【Pytorch】学习记录分享6——PyTorch经典网络 ResNet与手写体识别

【Pytorch】学习记录分享5——PyTorch经典网络 ResNet 1. ResNet &#xff08;残差网络&#xff09;基础知识2. 感受野3. 手写体数字识别3. 0 数据集&#xff08;训练与测试集&#xff09;3. 1 数据加载3. 2 函数实现&#xff1a;3. 3 训练及其测试&#xff1a; 1. ResNet &…

Gbase8c认证考试课后题

Gbase8c认证考试课后题 第一次练习 第一题 第二题 第三题 第四题 第五题 第六题 第七题 第八题 第九题 第十题 第十一题 第十二题 第十三题 第二次练习 第一题 第二题 第三题 第四题 第五题 第三次练习 第一题 第二题 第三题 第四题 第五题 第四次练习 第一题 第二题 第三…

关于外贸包裹的那些事

大早晨收到一个客户留言&#xff0c;询问能不能看一下他的货物包裹被送到了哪里&#xff0c;然后客户可以安排他的代理人联系去取包裹&#xff0c;我心里的第一感觉是难道包裹丢失了&#xff1f; 于是赶紧起来查看物流单号&#xff0c;单号显示早在半个多月前已经被他的国内代…

万界星空电机行业MES/电机mes

万界星空科技电机行业生产管理MES系统的主要功能&#xff1a; 1、基础数据管理 包含车间的产品材料清单管理&#xff0c;产品的工艺信息&#xff08;工艺、定额、工厂行程&#xff09;管理和资源信息&#xff08;关键设备信息&#xff09;等等。提供产品配套信息维护及查询&a…

conda环境下执行conda命令提示无法识别解决方案

1 问题描述 win10环境命令行执行conda命令&#xff0c;报命令无法识别&#xff0c;错误信息如下&#xff1a; PS D:\code\cv> conda activate pt conda : 无法将“conda”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&a…

vue3封装年份组件

ant框架年份组件 看了ant框架针对于年份不能自定义插槽内容所以放弃用ant框架年份组件&#xff0c;自定义插槽内容是想实现年份下方可以加小圆点的需求&#xff0c;因加小圆点需求必须实现&#xff0c;决定自己封装组件来实现需求&#xff0c;自己实现的效果呢类似于ant年份控件…

【数论】约数

试除法求约数 时间复杂度 O(sqrt(n))。 核心思路是求到较小的约数时&#xff0c;将其对应的较大约数也可以直接求出来&#xff0c; 例如&#xff1a;a/bc&#xff0c;b是a的余数&#xff0c;c也是a的余数 ps&#xff1a;注意bc的情况&#xff0c;要注意去重 void solve() …

数据库客户案例:每个物种都需要一个数据库!

1、GERDH——花卉多组学数据库 项目名称&#xff1a;GERDH&#xff1a;花卉多组学数据库 链接地址&#xff1a;https://dphdatabase.com 项目描述&#xff1a;GERDH包含了来自150多种园艺花卉植物种质的 12961个观赏植物。将不同花卉植物转录组学、表观组学等数据进行比较&am…

水利水库大坝安全监测参数详解

变形监测 变形监测是指对工程结构或地质环境中的变形进行实时或定期的测量与监测的过程。变形监测的目的是为了及时了解结构或环境的变形情况&#xff0c;评估其稳定性和安全性&#xff0c;并采取相应的措施来预防灾害和保护人民生命财产安全。 变形监测主要包括的内容有&#…

Jenkins 构建触发器指南

目录 触发远程构建 (例如&#xff0c;使用脚本) 描述 配置步骤 安全令牌 在其他项目构建完成后触发构建 描述 配置步骤 定时触发构建 描述 配置步骤 GitHub钩子触发GITScm轮询 描述 配置步骤 Poll SCM - 轮询版本控制系统 描述 触发远程构建 (例如&#xff0c;使…

Nature Commun.:物理所揭示原子分辨下的铁电涡旋畴的原位力学转变过程

通过复杂的晶格-电荷相互作用形成的铁电涡旋畴在纳米电子器件研发中具有巨大的应用潜力。实际应用中&#xff0c;如何在外界激励下操纵这类结构的拓扑状态是至关重要的。中国科学院物理研究所/北京凝聚态物理国家研究中心表面物理国家重点实验室与北京大学、湘潭大学和美国宾夕…

surface pro 如何调用和显示软键盘/触摸键盘

长按任务栏-勾选☑显示触摸键盘 右下角就会出现软键盘按钮 如果点选输入栏自动弹出或调用软键盘或触控键盘&#xff0c;按如下设定 设置-输入-不处于平板电脑模式且未连接键盘时显示触摸键盘 &#xff08;开关打开即可&#xff09;