Java NIO 详解

一、NIO简介

NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API,它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New,更多的是代表了 Non-blocking 非阻塞,NIO具有更高的并发性、可扩展性以及更少的资源消耗等优点。

二、NIO 与传统BIO

NIO:是同步非阻塞的,服务器实现模式为 一个线程处理多个连接。服务端只会创建一个线程复杂管理Selector(多路复用器),Selector(多路复用器)不断的轮询注册其上的Channel(通道)中的 I/O 事件,并将监听到的事件进行相应的处理。每个客户端与服务端建立连接时会创建一个 SocketChannel 通道,通过 SocketChannel 进行数据交互。
在这里插入图片描述

BIO:全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型,服务器实现模式为一个连接一个线程。每当客户端有连接请求时服务器端就需要启动一个线程进行处理。
在这里插入图片描述
两者主要区别如下:

  1. 阻塞和非阻塞:NIO 使用非阻塞式 I/O,而 BIO 使用阻塞式 I/O。在阻塞式 I/O 中,当一个 I/O 操作完成之前,线程会一直被阻塞,直到 I/O 操作完成;在非阻塞式 I/O 中,线程可以继续执行其他任务,直到 I/O 操作完成并返回结果。
  2. 线程模型:NIO 中的线程模型是基于事件驱动的,当一个 I/O 操作完成时,会触发相应的事件通知线程处理;而在 BIO 中,每个线程都负责处理一个客户端连接,需要不断地轮询客户端的输入输出流,以便及时响应客户端的请求。
  3. 内存消耗:NIO 中使用的缓冲区(Buffer)可以重复利用,减少了频繁的内存分配和回收,从而减少了内存的消耗;而在 BIO 中,每个客户端连接都需要单独分配一个缓冲区,容易造成内存的浪费。
  4. 并发性能:NIO 中使用非阻塞式 I/O,可以同时处理多个客户端连接,从而提高了并发处理能力;而在 BIO 中,由于每个客户端连接都需要一个线程来处理,当连接数量增加时,容易出现线程饥饿和资源耗尽的问题。
三、NIO 工作流程
  1. 创建 Selector:Selector 是 NIO 的核心组件之一,它可以同时监听多个通道上的 I/O 事件,并且可以通过 select() 方法等待事件的发生。
  2. 注册 Channel:通过 Selector 的 register() 方法将 Channel 注册到 Selector 上,这样 Selector 就可以监听 Channel 上的 I/O 事件。
  3. 等待事件:调用 Selector 的 select() 方法等待事件的发生,当有事件发生时,Selector 就会通知相应的线程进行处理。
  4. 处理事件:当事件发生时,Selector 会通知相应的线程进行处理,可以通过 Channel 读取数据,也可以通过 Channel 写入数据。
  5. 关闭 Channel:当 Channel 不再需要使用时,需要调用 Channel 的 close() 方法关闭 Channel,同时也需要调用 Buffer 的 clear() 方法清空 Buffer 中的数据,以释放内存资源。

Java NIO 的工作流程可以简单概括为:通过 Selector 监听多个 Channel 上的 I/O 事件,当事件发生时,通过对应的 Channel 进行读写操作,并在 Channel 不再需要使用时关闭 Channel。

四、NIO 核心的组件
1. Channel(通道)

Channel 是应用程序与操作系统之间交互事件和传递内容的直接交互渠道,应用程序可以从管道中读取操作系统中接收到的数据,也可以向操作系统发送数据。Channel和传统IO中的Stream很相似,其主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作。

1.1 常用的Channel实现类
  1. FileChannel:本地文件IO通道,从文件中读写数据。一般流程为:
1.获取文件通道,通过 FileChannel 的静态方法 open() 来获取,获取时需要指定文件路径和文件打开方式
FileChannel channel = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);2.创建字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);3.读/写操作
(1)、读操作
// 循环读取通道中的数据,并写入到 buf 中
while (channel.read(buf) != -1){ // 缓存区切换到读模式buf.flip(); // 读取 buf 中的数据while (buf.position() < buf.limit()){ // 将buf中的数据追加到文件中text.append((char)buf.get());}// 清空已经读取完成的 buffer,以便后续使用buf.clear();
}
(2)、写操作
// 循环读取文件中的数据,并写入到 buf 中
for (int i = 0; i < text.length(); i++) {// 填充缓冲区,需要将 2 字节的 char 强转为 1 自己的 bytebuf.put((byte)text.charAt(i)); // 缓存区已满或者已经遍历到最后一个字符if (buf.position() == buf.limit() || i == text.length() - 1) { // 将缓冲区由写模式置为读模式buf.flip(); // 将缓冲区的数据写到通道channel.write(buf); // 清空已经读取完成的 buffer,以便后续使用buf.clear(); }
}4.将数据刷出到物理磁盘
channel.force(false);5.关闭通道
channel.close();
  1. SocketChannel:网络套接字IO通道,TCP协议,客户端通过 SocketChannel 与服务端建立TCP连接进行通信交互。与传统的Socket操作不同的是,SocketChannel基于非阻塞IO模式,可以在同一个线程内同时管理多个通信连接,从而提高系统的并发处理能力。
1.打开一个 SocketChannel 通道
SocketChannel channel = SocketChannel.open();2.连接到服务端
channel.connect(new InetSocketAddress("localhost", 9001));3.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); 4.配置是否为阻塞方式(默认为阻塞方式)
channel.configureBlocking(false); // 配置通道为非阻塞模式5.将channel的连接、读、写等事件注册到selector中,每个chanel只能注册一个事件,最后注册的一个生效,
同时注册多个事件可以使用"|"操作符将常量连接起来
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE | SelectionKey.OP_READ);6.与服务端进行读写操作
channel.read(buf);
channel.write(buf);7.关闭通道
channel.close();
  1. ServerSocketChannel:网络套接字IO通道,TCP协议,服务端通过ServerSocketChannel监听来自客户端的连接请求,并创建相应的SocketChannel对象进行通信交互。ServerSocketChannel同样也是基于非阻塞IO模式,可以在同一个线程内同时管理多个通信连接,从而提高系统的并发处理能力。
1.打开一个 ServerSocketChannel 通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();2.绑定本地端口
serverChannel.bind(new InetSocketAddress(9001));3.配置是否为阻塞方式(默认为阻塞方式)
serverChannel.configureBlocking(false); // 配置通道为非阻塞模式4.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); 5.将serverChannel 的连接、读、写等事件注册到selector中,每个chanel只能注册一个事件,最后注册的一个生效,
同时注册多个事件可以使用"|"操作符将常量连接起来
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT| SelectionKey.OP_WRITE | SelectionKey.OP_READ);6.与客服端进行读写操作
serverChannel.read(buf);
serverChannel.write(buf);7.关闭通道
serverChannel.close();
  1. DatagramChannel:DatagramChannel是Java NIO中对UDP协议通信的封装。通过DatagramChannel对象,我们可以实现发送和接收UDP数据包。它与TCP协议不同的是,UDP协议没有连接的概念,所以无需像SocketChannel一样先建立连接再开始通信。
1.打开一个 DatagramChannel 通道
DatagramChannel channel = DatagramChannel.open();2.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024); 3.配置是否为阻塞方式(默认为阻塞方式)
channel.configureBlocking(false); // 配置通道为非阻塞模式4.与客服端进行读写操作
buffer.flip();
// 发送消息给服务端
channel.send(buffer, new InetSocketAddress("localhost", 9001));
buffer.clear();
// 接收服务端的响应信息
channel.receive(buffer);
buffer.flip();
// 打印出响应信息
while (buffer.hasRemaining()) {System.out.print((char) buffer.get());
}
buffer.clear();7.关闭通道
channel.close();
1.2 常用的Channel方法
  1. read(ByteBuffer):从 Channel 中读取数据到 ByteBuffer 中。如果 Channel 中没有可读数据,则会阻塞等待直到有数据可读。
  2. write(ByteBuffer):将数据写入到 Channel 中。如果 Channel 中没有可写空间,则会阻塞等待直到有可写空间。
  3. read(ByteBuffer, long):从 Channel 中读取数据到 ByteBuffer 中,并设置读取超时时间。如果超时时间到了还没有读取到数据,则会抛出 TimeoutException 异常。
  4. write(ByteBuffer, long):将数据写入到 Channel 中,并设置写入超时时间。如果超时时间到了还没有写入完成,则会抛出 TimeoutException 异常。
  5. flush():将 Channel 中的缓冲区数据刷新到底层设备中,如果没有数据需要刷新,则会立即返回。
  6. register(SelectionKey, int):将 Channel 注册到 Selector 上,并设置注册的事件类型和操作。可以通过 Selector 监听 Channel 上的事件,当有事件发生时,Selector 就会通知相应的线程进行处理。
  7. configureBlocking(boolean):设置 Channel 是否为阻塞模式。如果为阻塞模式,则在读取或写入数据时会一直阻塞等待,直到有数据可读或写入完成;如果为非阻塞模式,则在读取或写入数据时会立即返回,如果没有数据可读或写入完成,则会返回 -1。
  8. socket():获取底层的 Socket 对象。
  9. isConnected():判断 Channel 是否已经连接到了远程主机。
  10. isWritable():判断 Channel 是否可以写入数据。
  11. isReadable():判断 Channel 是否可以读取数据。
  12. isOpen():检查 Channel 是否已经打开。
  13. getRemoteAddress():获取 Channel 对应的远程地址。
  14. getLocalAddress():获取 Channel 对应的本地地址。
2. Buffer(缓冲区)

NIO 中的数据都是通过 Buffer 对象来处理的,每个 Buffer 对象都关联着一个字节数组,可以保存多个相同类型的数据。在读取数据时,是从Buffer 中读取的,在写入数据时,也是写入到 Buffer 中的。
在这里插入图片描述

2.1 Buffer 常用子类
  1. ByteBuffer:用于存储字节数据;
  2. CharBuffer:用于存储字符数据;
  3. ShortBuffer:用于存储Short类型数据;
  4. IntBuffer:用于存储Int类型数据;
  5. LongBuffer:用于存储Long类型数据;
  6. FloatBuffer:用于存储Float类型数据;
  7. DoubleBuffer:用于存储Double类型数据;
2.2 Buffer 重要属性
  1. capacity(容量):表示 Buffer 所占的内存大小,capacity不能为负,并且创建后不能更改。
  2. limit(限制):表示 Buffer 中可以操作数据的大小,limit不能为负,并且不能大于其capacity。写模式下,表示最多能往 Buffer 里写多少数据,即 limit 等于 Buffer 的capacity。读模式下,表示你最多能读到多少数据,其实就是能读到之前写入的所有数据。
  3. position(位置):表示下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制。初始的 position 值为 0,最大可为 capacity – 1。当一个 byte、long 等数据写到 Buffer 后, position 会向前移动到下一个可插入数据的 Buffer 单元。
  4. mark(标记):表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置。
    在这里插入图片描述
2.3 Buffer 常见方法
  1. clear():清空缓冲区并返回对缓冲区的引用;
  2. flip():将缓冲区的界限设置为当前位置,并将当前位置重置为 0;
  3. capacity():返回 Buffer 的 capacity 大小;
  4. limit():返回 Buffer 的界限(limit) 的位置;
  5. limit(int n):将设置缓冲区界限为 n,并返回一个具有新 limit 的缓冲区对象;
  6. position():返回缓冲区的当前位置 position;
  7. position(int n):将设置缓冲区的当前位置为 n, 并返回修改后的 Buffer 对象;
  8. mark():对缓冲区设置标记;
  9. reset():将位置 position 转到以前设置的mark 所在的位置;
  10. rewind():将位置设为为 0, 取消设置的 mark;
  11. hasRemaining():判断缓冲区中是否还有元素;
  12. get():读取单个字节;
  13. get(byte[] dst):读取多个字节;
  14. get(int index):读取指定索引位置的字节;
  15. put(byte b):将给定单个字节写入缓冲区的当前位置;
  16. put(byte[] src):将数组中的字节从当前位置依次写入到缓冲区中;
  17. put(int index, byte b):将指定字节写入缓冲区的索引位置;
2.4 Buffer 内存分配
  1. 普通缓冲区:通过allocate()方法进行分配,可以在jvm堆上申请堆上内存。如果要作IO操作,会先从本进程的堆上内存复制到直接内存,再利用本地IO处理。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
  1. 直接缓冲区:通过allocateDirect()方法进行分配,直接从本地内存中申请。如果要作IO操作,直接从本地内存中利用本地IO处理。使用直接内存会具有更高的效率,但是它比申请普通的堆内存需要耗费更高的性能。直接内存中的数据是在JVM之外的,因此它不会占用应用的内存,当有很大的数据要缓存,并且它的生命周期又很长,那么就比较适合使用直接内存。一般来说,如果不是能带来很明显的性能提升,还是推荐使用堆内存。
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(1024);
  1. 缓冲区分片:通过slice()方法可以根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer sliceBuffer = readBuffer.slice();
  1. 只读缓冲区:通过asReadOnlyBuffer()方法可以将任何常规缓冲区转换为只读缓冲区,这个方法返回 一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer readonlyBuffer = readBuffer.asReadOnlyBuffer();
3. Selector(选择器)

Selector 提供了选择已经就绪的任务的能力。Selector会不断的轮询注册在上面的所有channel,如果某个channel为读写等事件做好准备,那么就处于就绪状态,通过Selector可以不断轮询发现出就绪的channel,进行后续的IO操作。只要通过一个单独的线程就可以管理多个channel,从而管理多个网络连接。这就是Nio与传统I/O最大的区别,不用为每个连接都去创建一个线程。
在这里插入图片描述

3.1 Selector使用流程
1.获取选择器
Selector selector = Selector.open();2.通道注册到选择器,进行监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);3.获取可操作的 Channel
selector.select();4.获取可操作的 Channel 中的就绪事件集合
Set<SelectionKey> keys = selector.selectedKeys();5.处理就绪事件
while (keys.iterator().hasNext()){SelectionKey key = keys.iterator().next();if (!key.isValid()){continue;}if (key.isAcceptable()){accept(key);}if(key.isReadable()){read(key);}if (key.isWritable()){write(key);}keyIterator.remove(); //移除当前的key
}
3.2 SelectionKey事件类型

每个 Channel向Selector 注册时,都会创建一个 SelectionKey 对象,通过 SelectionKey 对象向Selector 注册,且 SelectionKey 中维护了 Channel 的事件。常见的四种事件如下:

  1. OP_READ:当操作系统读缓冲区有数据可读时就绪。
  2. OP_WRITE:当操作系统写缓冲区有空闲空间时就绪。
  3. OP_CONNECT:当 SocketChannel.connect()请求连接成功后就绪,该操作只给客户端使用。
  4. OP_ACCEPT:当接收到一个客户端连接请求时就绪,该操作只给服务器使用。
五、简单实例
1. 服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;public class NioServiceTest {private Selector selector;private ServerSocketChannel serverSocketChannel;private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓冲区大小为1024字节private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);String str;public NioServiceTest(int port) throws IOException {// 打开服务器套接字通道this.serverSocketChannel = ServerSocketChannel.open();// 服务器配置为非阻塞 即异步IOthis.serverSocketChannel.configureBlocking(false);// 绑定本地端口this.serverSocketChannel.bind(new InetSocketAddress(port));// 创建选择器this.selector = Selector.open();// 注册接收连接事件this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);}public void handle() throws IOException {// 无限判断当前线程状态,如果没有中断,就一直执行while内容。while(!Thread.currentThread().isInterrupted()){// 获取准备就绪的channelif (selector.select() == 0) {continue;}// 获取到对应的 SelectionKey 对象Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = keys.iterator();// 遍历所有的 SelectionKey 对象while (keyIterator.hasNext()){// 根据不同的SelectionKey事件类型进行相应的处理SelectionKey key = keyIterator.next();if (!key.isValid()){continue;}if (key.isAcceptable()){accept(key);}if(key.isReadable()){read(key);}// 移除当前的keykeyIterator.remove();}}}/*** 客服端连接事件处理** @param key* @throws IOException*/private void accept(SelectionKey key) throws IOException {SocketChannel socketChannel = this.serverSocketChannel.accept();socketChannel.configureBlocking(false);// 注册客户端读取事件到selectorsocketChannel.register(selector, SelectionKey.OP_READ);System.out.println("client connected " + socketChannel.getRemoteAddress());}/*** 读取事件处理** @param key* @throws IOException*/private void read(SelectionKey key) throws IOException{SocketChannel socketChannel = (SocketChannel) key.channel();//清除缓冲区,准备接受新数据this.readBuffer.clear();int numRead;try{// 从 channel 中读取数据numRead = socketChannel.read(this.readBuffer);}catch (IOException e){System.out.println("read failed");key.cancel();socketChannel.close();return;}str = new String(readBuffer.array(),0,numRead);System.out.println("read String is: " + str);}public static void main(String[] args) throws Exception {System.out.println("sever start...");new NioServiceTest(8000).handle();}
}
2. 客户端
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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;public class NioClientTest {ByteBuffer writeBuffer = ByteBuffer.allocate(1024);private SocketChannel sc;private Selector selector;public NioClientTest(String hostname, int port) throws IOException {// 打开socket通道sc = SocketChannel.open();// 配置为非阻塞 即异步IOsc.configureBlocking(false);// 连接服务器端sc.connect(new InetSocketAddress(hostname,port));// 创建选择器selector = Selector.open();// 注册请求连接事件sc.register(selector, SelectionKey.OP_CONNECT);}public void send() throws IOException{Scanner scanner = new Scanner(System.in);// 无限判断当前线程状态,如果没有中断,就一直执行while内容。while (!Thread.currentThread().isInterrupted()){// 获取准备就绪的channelif (selector.select() == 0) {continue;}// 获取到对应的 SelectionKey 对象Set<SelectionKey> keys = selector.selectedKeys();System.out.println("all keys is:"+keys.size());Iterator<SelectionKey> iterator = keys.iterator();// 遍历所有的 SelectionKey 对象while (iterator.hasNext()){SelectionKey key = iterator.next();//判断此通道上是否在进行连接操作if (key.isConnectable()){sc.finishConnect();//注册写操作sc.register(selector, SelectionKey.OP_WRITE);System.out.println("server connected...");break;}else if (key.isWritable()){System.out.println("please input message:");String message = scanner.nextLine();writeBuffer.clear();writeBuffer.put(message.getBytes());//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位writeBuffer.flip();sc.write(writeBuffer);}// 移除当前的keyiterator.remove();}}}public static void main(String[] args) throws Exception {new NioClientTest("localhost", 8000).send();}
}

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

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

相关文章

es head 新增字段、修改字段、批量修改字段、删除字段、删除数据、批量删除数据

目录 一、新增字段 二、修改字段值 三、批量修改字段值 ​四、删除字段 五、删除数据/文档 六、批量删除数据/文档 一、新增字段 put http://{ip}:{port}/{index}/_mapping/{type} 其中&#xff0c;index是es索引、type是类型 数据&#xff1a; {"_doc"…

数据结构与算法之美学习笔记:20 | 散列表(下):为什么散列表和链表经常会一起使用?

目录 前言LRU 缓存淘汰算法Redis 有序集合Java LinkedHashMap解答开篇 & 内容小结 前言 本节课程思维导图&#xff1a; 今天&#xff0c;我们就来看看&#xff0c;在这几个问题中&#xff0c;散列表和链表都是如何组合起来使用的&#xff0c;以及为什么散列表和链表会经常…

window 搭建 MQTT 服务器并使用

1. 下载 安装 mosquitto 下载地址&#xff1a; http://mosquitto.org/files/binary/ win 使用 win32 看自己电脑下载相应版本&#xff1a; 一直安装&#xff1a; 记住安装路径&#xff1a;C:\Program Files\mosquitto 修改配置文件&#xff1a; allow_anonymous false 设置…

【VSCode】Visual Studio Code 下载与安装教程

前言 Visual Studio Code&#xff08;简称 VS Code&#xff09;是一个轻量级的代码编辑器&#xff0c;适用于多种编程语言和开发环境。本文将介绍如何下载和安装 Visual Studio Code。 下载安装包 首先&#xff0c;我们需要从官方网站下载 Visual Studio Code 的安装包。请访…

Docker与VM虚拟机的区别以及Docker的特点

01、本质上的区别 VM(VMware)在宿主机器、宿主机器操作系统的基础上创建虚拟层、虚拟化的操作系统、虚拟化的仓库&#xff0c;然后再安装应用&#xff1b; Container(Docker容器)&#xff0c;在宿主机器、宿主机器操作系统上创建Docker引擎&#xff0c;在引擎的基础上再安装应…

sqli-labs(Less-4) extractvalue闯关

extractvalue() - Xpath类型函数 1. 确认注入点如何闭合的方式 2. 爆出当前数据库的库名 http://127.0.0.1/sqlilabs/Less-4/?id1") and extractvalue(1,concat(~,(select database()))) --3. 爆出当前数据库的表名 http://127.0.0.1/sqlilabs/Less-4/?id1") …

Prometheus+Grafana环境搭建(window)

PrometheusGrafana环境搭建 1&#xff1a;配置Prometheus 1.1: 下载Prometheus安装包 官方下载地址 找到对应的win版本进行下载并解压 1.2 下载Window数据采集 官方下载地址 下载以管理员运行&#xff0c;安装成功后在服务里会出现一个"windows_exporter"采集…

HCL设备启动失败——已经解决

摸索了一个多小时&#xff0c;终于搞定了&#xff0c;首先HCL这款软件是需要安装Oracle VM Visual Box的&#xff0c;小伙伴们安装的时候记得点击安装Visual Box&#xff1b; 安装完后显示设备不能启动&#xff0c;然后我根据这个 HCL模拟器中Server设备启动失败的解决办法_hc…

【原创】java+swing+mysql校园活动管理系统设计与实现

前言&#xff1a; 本文介绍了一个校园活动管理系统的设计与实现。该系统基于JavaSwing技术&#xff0c;采用C/S架构&#xff0c;使用Java语言开发&#xff0c;以MySQL作为数据库。系统实现了活动发布、活动报名、活动列表查看等功能&#xff0c;方便了校园活动的发布和管理&am…

虚幻C++ day5

角色状态的常见机制 创建角色状态设置到UI上 在MainPlayer.h中新建血量&#xff0c;最大血量&#xff0c;耐力&#xff0c;最大耐力&#xff0c;金币变量&#xff0c;作为角色的状态 //主角状态UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category "Playe Stats&…

Python in Visual Studio Code 2023年11月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Python 和 Jupyter 扩展将于 2023 年 11 月发布&#xff01; 此版本包括以下公告&#xff1a; 改进了使用 Shift Enter 在终端中运行当前行弃用内置 linting 和格式设置功能对 Python linting 扩展的改进重…

Appium移动自动化测试--安装Appium

Appium 自动化测试是很早之前就想学习和研究的技术了&#xff0c;可是一直抽不出一块完整的时间来做这件事儿。现在终于有了。 反观各种互联网的招聘移动测试成了主流&#xff0c;如果再不去学习移动自动化测试技术将会被淘汰。 web自动化测试的路线是这样的&#xff1a;编程语…

asp.net core mvc之 RAZOR共享指令和标签助手 TagHelpers

一、RAZOR共享指令 RAZOR共享指令&#xff1a;在视图中导入命名空间&#xff0c;执行依赖注入。 RAZOR共享指令是写在 Views目录下的 _ViewImports.cshtml 文件 支持指令如下&#xff1a; addTagHelper 增加标签助手 removeTagHelper 移除标签助手 tagHelperPrefix 标签助手…

防抖-节流-深拷贝-事件总线

一、防抖与节流 1.认识防抖与节流函数 防抖和节流的概念其实最早并不是出现在软件工程中&#xff0c;防抖是出现在电子元件中&#xff0c;节流出现在流体流动中 而JavaScript是事件驱动的&#xff0c;大量的操作会触发事件&#xff0c;加入到事件队列中处理。而对于某些频繁…

【Java 进阶篇】揭秘 JQuery 广告显示与隐藏:打造令人惊艳的用户体验

在当今互联网时代&#xff0c;广告已经成为网页中不可忽视的一部分。然而&#xff0c;如何通过巧妙的交互设计&#xff0c;使广告既能吸引用户的眼球&#xff0c;又不会给用户带来干扰&#xff0c;成为了许多前端开发者需要思考的问题之一。在这篇博客中&#xff0c;我们将深入…

长短期记忆(LSTM)与RNN的比较:突破性的序列训练技术

长短期记忆&#xff08;Long short-term memory, LSTM&#xff09;是一种特殊的RNN&#xff0c;主要是为了解决长序列训练过程中的梯度消失和梯度爆炸问题。简单来说&#xff0c;就是相比普通的RNN&#xff0c;LSTM能够在更长的序列中有更好的表现。 Why LSTM提出的动机是为了解…

创邻科技亮相ISWC 2023,国际舞台见证知识图谱领域研究突破

近日&#xff0c;第22届国际语义网大会 ISWC 2023 在雅典希腊召开&#xff0c;通过线上线下的形式&#xff0c;聚集了全球的顶级研究人员、从业人员和行业专家&#xff0c;讨论、发展和塑造语义网和知识图谱技术的未来。创邻科技CEO张晨博士作为知识图谱行业专家受邀参会&#…

最新完美版积分商城系统-奇偶商城系统源码+独立代理后台+附搭建教程

源码简介&#xff1a; 最新完美版积分商城系统&#xff0c;网购商城系统源码&#xff0c;是更新的奇偶商城系统源码&#xff0c;它拥有独立代理后台&#xff0c;而且内附搭建教程。 1.演示环境&#xff1a;Linux Centos7以上版本 宝塔 2.Nginx 1.18.0 PHP7.0 Mysql5.6 3…

CTFd-Web题目动态flag

CTFd-Web题目动态flag 1. dockerhub注册2. dockerfile编写3. 上传到docker仓库4. 靶场配置5. 动态flag实现 1. dockerhub注册 想要把我们的web题目容器上传到docker仓库中&#xff0c;我们需要dockerhub官网注册一个账号&#xff0c;网址如下 https://hub.docker.com/2. dock…

vue-数据双向绑定原理

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-数据双向绑定原理 目录 虚拟DOM与Diff算法 1. 对虚拟DOM的理解&#xff1f; 2. 虚拟DOM的解…