Java NIO 详解

Java 从1.4开始引入NIO(New IO),是一种基于块(Block)的IO机制,也称为非阻塞IO。相比于传统的Java IO(IO流)方式,Java NIO提供了更快速、高效、灵活的IO操作。

Java NIO的核心组件包括以下几个部分:

  1. Channel(通道):Channel是Java NIO的基础,代表了一个与IO设备(如文件、套接字)交互的双向通信通道。它可以读取和写入数据。Channel可以通过Selector来实现非阻塞IO操作。

  2. Buffer(缓冲区):Buffer是一个用来存储数据的对象,NIO的读写操作都是基于缓冲区的。它提供一组方法来读写数据,并且在读写过程中维护缓冲区的状态信息。

  3. Selector多路复用选择器):Selector是一个用于多路复用的对象,它可以同时监控多个Channel的状态,以便在有IO事件到来时通知程序进行处理。通过Selector,可以使单个线程就可以处理多个Channel的IO操作。

  4. Non-blocking IO(非阻塞IO):Java NIO提供了非阻塞IO的特性,即在等待IO操作完成时,线程不会被阻塞,可以继续执行其他任务。这样,一个线程可以同时处理多个Channel的IO操作,提高了系统的吞吐量和响应性能。

Java NIO相对于传统的Java IO方式,具有如下优势:

  1. 更快速的IO操作:通过使用缓冲区和非阻塞IO,Java NIO能够更高效地进行数据读写操作。

  2. 处理多个连接:使用Selector可以单线程处理多个连接,提高系统的并发能力和资源利用率。

  3. 异步IO:Java NIO还提供了一些异步IO的方式,通过回调或者Future来实现。

Channel : 

java.nio.channels.Channel 是Java NIO的基础,代表了一个与IO设备(如文件、Socket)交互的通道。常用的实现类包括:

  1. FileChannel : 用于读写文件中的数据,可以从文件中读取字节数据到Buffer,或将Buffer中的数据写入文件。

  2. SocketChannel:用于通过 TCP 协议进行网络数据的读写操作,可以与远程Socket建立连接,并进行读写操作。

  3. ServerSocketChannel:用于监听并接受客户端连接请求。

  4. DatagramChannel:用于通过 UDP 协议进行网络数据的读写操作。

FileChannel 的使用:        

用于读写文件中的数据,可以从文件中读取字节数据到Buffer,或将Buffer中的数据写入文件。

1、打开FileChannel 

1.1 通过FileInputStream或FileOutputStream获取FileChannel

FileInputStream fis = new FileInputStream("path/to/file");
FileChannel channel = fis.getChannel();FileOutputStream fos = new FileOutputStream("path/to/file");
FileChannel channel = fos.getChannel();

1.2  通过RandomAccessFile获取FileChannel: 

RandomAccessFile file = new RandomAccessFile("path/to/file", "rw");
FileChannel channel = file.getChannel();

1.3  使用java.nio.file.Files工具类获取FileChannel:open(Path path, OpenOption... options)

Path path = Paths.get("path/to/file");
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);

Java中的Files类是java.nio.file包提供的一个实用工具类,用于进行文件和目录的各种操作。它包含了大量的静态方法,用于操作文件、目录、路径等。Files类的一些常用方法:

Path path = Paths.get("path/to/file");// 判断文件或目录是否存在:exists(Path path)
boolean exists = Files.exists(path);// 创建文件:createFile(Path path, FileAttribute<?>... attrs) 
Files.createFile(path);// 删除文件或目录:delete(Path path) 
Files.delete(path);// 创建目录:createDirectory(Path dir, FileAttribute<?>... attrs)
Path dir = Paths.get("path/to/directory");
Files.createDirectory(dir);Path source = Paths.get("path/to/source");
Path target = Paths.get("path/to/target");
// 复制文件或目录:copy(Path source, Path target, CopyOption... options)
Files.copy(source, target);// 移动/重命名文件或目录:move(Path source, Path target, CopyOption... options)
Files.move(source, target);// 写入内容到文件:writeString(Path path, CharSequence csq, Charset charset, OpenOption... options) 
String content = "Hello, World!";
Files.writeString(path, content, StandardCharsets.UTF_8);// 获取文件属性:readAttributes(Path path, Class<A> type, LinkOption... options)
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

1.4 通过java.nio.file.FileSystems工具类获取FileChannel

// 返回代表默认文件系统的FileSystem对象
FileSystem fs = FileSystems.getDefault();
Path path = fs.getPath("path/to/file");
FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE);

FileSystems类提供了一些方便的方法,用于获取默认文件系统、根据URI或Path构建文件系统以及获取文件系统提供程序。它们可以用于实现更灵活多样的文件系统操作。需要注意的是,使用FileSystems类时,需要考虑安全性、权限和适用性等因素,以确保操作正确可靠。如: 

// 根据URI获取文件系统 newFileSystem(URI uri, Map<String, ?> env)
URI uri = new URI("file:/path/to/directory/");
FileSystem fs = FileSystems.newFileSystem(uri, null);//根据Path获取文件系统 newFileSystem(Path path, ClassLoader loader)
Path path = Paths.get("/path/to/jarfile.jar");
FileSystem fs2 = FileSystems.newFileSystem(path, null);// 获取支持的文件系统提供程序:newFileSystemProvider(Class<? extends FileSystem> type)
FileSystemProvider provider = FileSystems.newFileSystemProvider(FTPFileSystem.class);

1.5 通过FileDescriptor获取FileChannel

FileInputStream fis = new FileInputStream("path/to/file"); 
FileDescriptor fd = fis.getFD();
FileChannel channel = new FileInputStream(fd).getChannel();

在Java中,FileDescriptor类表示一个文件描述符,它是与底层操作系统文件句柄相关联的标识符。FileDescriptor主要用于在Java程序中直接操作底层文件句柄,例如创建FileInputStream、FileOutputStream等。

2、从FileChannel读取数据到缓冲区

// 创建一个大小为1024 bit 的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// FileChannel 数据读到缓冲区
int bytesRead = channel.read(buffer);

3、将数据从缓冲区写入到FileChannel

// flip() 将缓冲区 读/写 模式切换
buffer.flip();
while (buffer.hasRemaining()) {channel.write(buffer);
}
buffer.clear();

4、关闭FileChannel

channel.close();

5、其它操作

  1. 移动FileChannel的位置:
    // 移动文件指针位置到当前位置后的50个字节
    long newPosition = channel.position() + 50; 
    channel.position(newPosition);
  2. 截断文件(截短或扩展文件长度)
    channel.truncate(1024); // 将文件截断为1024个字节
  3. 强制将FileChannel的内容刷新到磁盘:
    channel.force(true); // 强制刷新文件数据到磁盘

​​​​​​​​2、SocketChannel 使用

        SocketChannel是用于进行套接字通信的重要组件,它提供了非阻塞的、基于缓冲区的I/O操作。你可以使用SocketChannel来建立连接、发送和接收数据,以及关闭连接等操作。

// 打开一个SocketChannel实例
SocketChannel socketChannel = SocketChannel.open();
// 切换为非阻塞模式
channel.configureBlocking(false);// 连接远程服务器:使用connect()方法连接到远程服务器
InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 9001);
socketChannel.connect(remoteAddress);while (!socketChannel.finishConnect()) { // 等待连接完成// 写入数据到SocketChannelByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("要发送的数据".getBytes());buffer.flip();  // 将缓冲区切换为读模式socketChannel.write(buffer);// 从SocketChannel中读取数据到ByteBufferByteBuffer buffer2 = ByteBuffer.allocate(1024);int bytesRead = channel.read(buffer2);
}channel.close();

3、ServerSocketChannel 使用

        ServerSocketChannel是一种用于监听传入连接的通道。它作为服务器端通道,用于接受客户端的连接请求。

// 打开ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 切换为非阻塞模式 [根据情况选择是否要这么处理]
serverChannel.configureBlocking(false);//绑定端口和地址
serverChannel.bind(new InetSocketAddress("localhost", 8080));// 接受客户端的连接请求。该方法默认会阻塞,直到有客户端连接到达,返回一个SocketChannel对象
// 如果切换到了非阻塞, 这里就不会阻塞
SocketChannel clientChannel = serverChannel.accept();serverChannel.close();

4、DatagramChannel 使用

DatagramChannel是一种用于进行UDP协议通信的通道。它可以发送和接收UDP数据报

// 打开DatagramChannel
DatagramChannel channel = DatagramChannel.open();// 绑定IP和端口
channel.bind(new InetSocketAddress("localhost", 8080));// 该方法会阻塞,直到接收到数据报,返回一个SocketAddress和接收到的数据报的数量
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketAddress address = channel.receive(buffer);//发送数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, Server!".getBytes());
buffer.flip();
int bytesSent = channel.send(buffer, new InetSocketAddress("example.com", 8080));// 配置阻塞模式, 默认情况下是阻塞模式
channel.configureBlocking(true);channel.close();

Buffer : 

Buffer介绍:

        java.nio.Buffer 用于读写数据,是Java NIO读写操作的中间容器。​​​​​​​数据从通道读入缓冲区,从缓冲区写入通道。

        缓冲区本质上是一块可以写入和读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便该块的内存访问​​​​​​​。缓冲区实际上就是一个容器对象/数组。 

  1. 常用的Buffer子类包括:

    1. ByteBuffer:ByteBuffer是最常用的Buffer实现类,用于读写字节数据。

    2. CharBuffer:用于读写字符数据

    3. ShortBuffer/ IntBuffer/ LongBuffer/ FloatBuffer/ DoubleBuffer:用于读写特定类型数据

  2. Buffer的主要特性:

    1. 容量(Capacity):缓冲区的容量表示它可以存储的最大数据量,一旦缓冲区被创建,其容量不能更改。
    2. 位置(Position):读写操作的位置,表示下一个要读取或写入的元素的索引
    3. 上界(Limit):缓冲区当前有效数据的边界,即下一个要读取或写入的元素的索引。

    4. 标记(Mark):记住某个特定位置的索引,可以通过 mark()和 reset()方法来返回该位置。

    5. 读写模式切换:缓冲区可以处于读模式(读取数据到缓冲区)或写模式(将数据从缓冲区写出)。

Buffer使用: 

        1、创建Buffer,分配空间:通过调用Buffer的静态方法allocate()来分配指定容量的缓冲区。

// 分配一个容量为1024字节的ByteBuffer实例
ByteBuffer buffer = ByteBuffer.allocate(1024); 

        2、写入数据到缓冲区:使用Buffer的put()方法将数据写入缓冲区

// 写入一个字节数据到缓冲区
buffer.put((byte) 65); 

        3、转换为读模式:通过调用Buffer的flip()方法,将Buffer切换为读取模式。在读模式下,可以读取缓冲区中的数据。

// 切换为读取模式
buffer.flip(); 

        4、从缓冲区读取数据:使用Buffer的get()方法读取缓冲区中的数据。

// 从缓冲区读取一个字节数据
byte data = buffer.get(); 

        5、重复读取:Buffer的rewind()方法将缓冲区切换为读模式,但保留之前读取到的数据。然后再次使用get()方法读取数据。

// 重复读取前需要调用rewind()方法
buffer.rewind(); // 重新读取一个字节数据
byte data = buffer.get(); 

        6、清空缓冲区:

  • clear()方法,将Buffer切换为写入模式,并清空缓冲区的数据。在写入模式下,可以写入新的数据; 
  • compact()方法:相比clear()方法,compact()方法会清除已经读取的数据,但是会保留未读取的数据。未读取的数据会被移动到缓冲区的开头,可以继续写入新的数据。

Buffer的其他API: 

  1. int capacity() : 获取缓冲区的大小
  2. int limit() :获取缓冲区的限制位置,即缓冲区中可读写的数据范围。写入模式下,默认等于缓冲区的容量;读取模式下,默认等于写入模式下的位置
  3. Buffer limit( int newLimit):设置缓冲区的限制位置
  4. int position() : 获取缓冲区的当前位置,即读取或写入数据的位置
  5. Buffer position( int newPosition):设置当前位置的绝对位置
  6. boolean hasRemaining() : 判断缓冲区是否还有未读取的数据
  7. void mark(): 将当前位置设置为标记位置
  8. void reset() : 重置位置为标记位置
  9. void flip() : 切换为读取模式 
  10. void clear() :清空缓冲区,切换为写入模式
  11. void compact() : 压缩缓冲区,将未读取的数据移到开头
  12. void rewind() : 重绕缓冲区,切换为读取模式

Selector:

        java.nio.channels.Selector 是一种用于多路复用的对象,用于管理多个通道的I/O事件。它允许单个线程同时监视多个通道,以及在有准备就绪的通道上进行读写操作,从而提高系统的性能和可伸缩性。

 

Selector的工作原理如下:

  1. 首先,通过Selector.open()创建一个Selector实例。

  2. 然后,将需要进行IO操作的通道注册到Selector上,通过调用通道的register()方法完成注册,此时Selector上生成一个SelectionKey。通道是抽象类SelectableChannel,其子类有: SocketChannel、ServerSocketChannel、DatagramChannel 等。

  3. Selector调用select()方法进行阻塞,等待注册的通道中有事件发生。当有一个或多个通道有事件发生时,select()方法返回,并返回发生事件的通道的数量。

  4. 根据返回的数量,可以通过selectedKeys()方法获取发生事件的通道的集合,然后进行相应的IO操作。

  5. 建议在处理完一个通道的事件后,调用selectedKeys()的remove()方法将该通道从集合中移除,以避免重复处理。

Selector使用示例: 

Selector selector = Selector.open();
SelectableChannel channel = SocketChannel.open();
channel.configureBlocking(false); // 非阻塞模式
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);while (true) {int readyChannels = selector.select();if (readyChannels == 0) {continue;}Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isReadable()) {// 处理可读事件// ...}keyIterator.remove();}
}

Selector API 

  1. static Selector open(): 创建一个新的Selector对象。
  2. int select(): 阻塞等待就绪的通道,返回已就绪通道的数量
  3. int select(long timeout): 最多阻塞timeout毫秒,等待就绪的通道
  4. int selectNow(): 非阻塞立即返回就绪的通道数量
  5. Set<SelectionKey> selectedKeys(): 获取就绪通道的SelectionKey集合
  6. Set<SelectionKey> keys(): 获取所有注册通道的SelectionKey集合
  7. Selector wakeup() :唤醒其他阻塞线程的 select 方法立刻执行并返回
  8. void close() :关闭Selector 

其它与Selector相关的API 

Selector 的使用过程中, SelectableChannel 和 SelectionKey 一直贯穿着Selector的始终,SelectableChannel 是注册进Selector的通道, SelectionKey 是通道注册进Selector后生成对象,里面包含了通道注册信息和进程状态信息。

  1. SelectableChannel  的 API : 
    1. SelectionKey register(Selector sel, int ops):将通道注册到Selector上,同时指定关注的事件类型,常用的时间类型有:
      1. SelectionKey.OP_READ = 1 << 0:读事件
      2. SelectionKey.OP_WRITE = 1 << 2:写事件
      3. SelectionKey.OP_CONNECT = 1 << 3:连接事件
      4. SelectionKey.OP_ACCEPT = 1 << 4 :接收事件
    2. SelectableChannel configureBlocking(boolean block) : 设置通道为【非】阻塞模
    3. boolean isRegistered(): 判断通道是否已经注册到Selector上
    4. boolean isBlocking() : 通道是否阻塞
    5. SelectionKey keyFor(Selector sel) :返回通道在Selector中最后一次注册的信息
    6. Object blockingLock() :返回的是通道对象关联的锁对象,用于同步操作阻塞模式切换。它并不是用于直接控制通道的阻塞/非阻塞状态。
  2. SelectionKey 的API :SelectionKey类表示一个通道在选择器上的注册信息。 
    1. SelectableChannel channel() : 获取关联的通道
    2. Selector selector() :获取关联的选择器
    3. boolean isReadable() | isWritable() | isConnectable() | isAcceptable() : 判断通道是否可读、写、连接、接受连接
    4. int readyOps() : 获取准备就绪的操作集合
    5. int interestOps() : 获取所有注册的操作集合
    6. SelectionKey interestOps(int ops) : 修改操作集合
    7. void cancel() : 取消注册
    8. Object attach(Object ob) : 这是关联的附加对象
    9. Object attachment() : 获取关联的附加对象

Scatter/Gather

Java NIO(New I/O)中的Scatter/Gather是一种用于处理I/O操作的重要模式,通过将数据分散读取(Scatter Read)或聚集写入(Gather Write),可以有效地处理复杂的数据结构。在Scatter/Gather模式下,可以一次性读取多个数据块到多个缓冲区,或将多个缓冲区中的数据一次性写入到目标通道。

1. Scatter Read(分散读取)

  • 使用多个缓冲区准备好接收数据。
  • 调用通道的read()方法,将数据按照缓冲区的顺序依次读取到各个缓冲区中。
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = { buffer1, buffer2 };channel.read(buffers);

2. Gather Write(聚集写入)

  • 使用多个缓冲区存储要写入的数据。
  • 调用通道的write()方法,将多个缓冲区中的数据一起写入到目标通道。
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = { buffer1, buffer2 };channel.write(buffers);

Scatter/Gather模式可以用于处理复杂的数据结构,例如由多个部分组成的消息。通过Scatter将数据分散到多个缓冲区,可以对消息的各个部分进行独立处理。而通过Gather将多个缓冲区中的数据聚集写入到目标通道,可以将多个部分组装成完整的消息并进行发送。

需要注意的是,Scatter/Gather模式依赖于底层通道的能力支持。并非所有的通道都支持Scatter/Gather操作,只有实现了`ReadableByteChannel`(可读通道)或`WritableByteChannel`(可写通道)接口的通道才能进行Scatter/Gather操作。

总结: 

 Java NIO还提供了其他一些类和接口,如FileLock、MappedByteBuffer、Pipe等,用于实现更复杂的IO操作和功能。

需要注意的是,Java NIO的使用与传统的Java IO方式有很大的区别,它对于开发者而言更加复杂。但在高并发、大规模等场景下,Java NIO可以提供更好的性能和可扩展性。

IO 与 NIO对比
IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO非阻塞IO
(无)选择器(Selectors)

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

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

相关文章

Aduino中eps环境搭建

这里只记录Arduino2.0以后版本&#xff1a;如果有外网环境&#xff0c;那么可以轻松搜到ESP32开发板环境并安装&#xff0c;如果没有&#xff0c;那就见下面操作&#xff1a; 进入首选项&#xff0c;将esp8266的国内镜像地址填入&#xff0c;然后保存&#xff0c;在开发板中查…

代码随想录算法训练营第二十九天 | Leetcode随机抽题检测

Leetcode随机抽题检测 160 相交链表未看解答自己编写的青春版重点题解的代码206 反转链表 一段用于复制的标题未看解答自己编写的青春版重点题解的代码日后再次复习重新写 234 回文链表未看解答自己编写的青春版重点综上&#xff0c;利用快慢指针找寻链表中间&#xff0c;就按加…

牛客网Verilog刷题——VL51

牛客网Verilog刷题——VL51 题目答案 题目 请编写一个十六进制计数器模块&#xff0c;计数器输出信号递增每次到达0&#xff0c;给出指示信号zero&#xff0c;当置位信号set 有效时&#xff0c;将当前输出置为输入的数值set_num。模块的接口信号图如下&#xff1a; 模块的时序图…

JVM基础篇-方法区与运行时常量池

JVM基础篇-方法区与运行时常量池 方法区 Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于传统语言的编译代码的存储区或者类似于操作系统进程中的“文本”段。它存储每个类的结构&#xff0c;例如运行时常量池、字段和方法数据&#xff0c;以及方法和…

Hadoop 集群如何升级?

前言 本文隶属于专栏《大数据技术体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见大数据技术体系 正文 升级 Hadoop 集群需要细致的规划&#xff0c;特…

使用docker部署Wordpress

文章目录 1.创建网络2.创建volume存储3.拉取镜像4.创建mysql容器mysql修改密码 5.创建wordpress容器6.访问localhost:80就可以直接使用啦 1.创建网络 docker network create --subnet172.18.0.0/24 pro-net2.创建volume存储 # mysql 存储 docker volume create volume_mysql…

vscode 前端开发插件 2023

自己记录 安装vscode后必装插件 chinesegit 必装没啥可说 随时更新 1.CSS Navigation CTRL点击类名可跳转到对应样式位置。 如果是scss less的话。css peak插件无法生效 2.GitLens — Git supercharged 可以看到每一行的git提交记录。 3.Auto Rename Tag 可以同步更新…

winform学习(3)-----Windows窗体应用和Windows窗体应用(.Net Framework)有啥区别?

1.模板选择 在学习winform的时候总是会对这两个应用不知道选择哪个&#xff1f;而且在学习的时候也没有具体的说明 首先说一下我是在添加控件的时候出现了以下问题 对于使用了Windows窗体应用这个模板的文件在工具箱中死活不见控件。 在转换使用了Windows窗体应用(.NET Fram…

Linux学习之一次性计划任务at

计划任务&#xff1a; 让计算机在指定的时间运行程序的任务 计划任务的分类&#xff1a; 1&#xff09;一次性计划任务 2&#xff09;周期性计划任务 先来讲讲一次性执行任务at。执行at 18:32报错-bash: at: command not found。 yum install -y at安装at。 at 18:32后边按下…

【C++】二叉搜索树

二叉搜索树 前言正式开始模拟实现树节点以及树框架增中序遍历查找删除 递归实现增删查查插删 析构拷贝构造赋值重载时间复杂度分析应用场景两道题 前言 本来想先把搁置了一个月的Linux讲讲的&#xff0c;但是里面有些内容需要用到一些比较高级的数据结构&#xff0c;用C写的话…

【业务功能篇61】SpringBoot项目流水线 dependencyManagement 标签整改依赖包版本漏洞问题

业务场景&#xff1a;当前我们项目引入了公司自研的一些公共框架组件&#xff0c;比如SSO单点登录jar包&#xff0c;文件上传服务jar包等公共组件&#xff0c;开发新功能&#xff0c;本地验证好之后&#xff0c;部署流水线&#xff0c;报出一些jar包版本的整改漏洞问题&#xf…

二叉树OJ(C)

文章目录 1.单值二叉树1.1法一&#xff1a;无返回值1.2法二&#xff1a;有返回值 2.相同的树3.对称二叉树4.二叉树的前序遍历5.二叉树的中序遍历6.二叉树的后序遍历7.另一棵树的子树8.二叉树遍历 1.单值二叉树 1.1法一&#xff1a;无返回值 struct TreeNode {int val;struct …

Virtualbox虚拟机中Ubuntu忘记密码

1、首先重新启动Ubuntu系统&#xff0c;鼠标快速点一下Virtualbox虚拟机窗口获取焦点&#xff0c;然后按住shift键&#xff0c;以调出grub启动菜单。 2、根据提示按下键盘E键进入编辑模式&#xff0c;向下移动光标&#xff0c;将如下"ro quiet splash $vt_handoff"部…

SpringBoot集成jasypt,加密yml配置文件

SpringBoot集成jasypt&#xff0c;加密yml配置文件 一、pom配置二、生成密文代码三、配置3.1、yml加密配置3.2、密文配置3.3、启动配置3.4、部署配置 四、遇到的一些坑 最新项目安全检测&#xff0c;发现配置文件中数据库密码&#xff0c;redis密码仍处理明文状态 一、pom配置…

一套AI+医疗模式的医院智慧导诊系统源码:springboot+redis+mybatis plus+mysql

一套AI医疗模式的医院智慧导诊系统源码 相关技术&#xff1a; 技术架构&#xff1a;springbootredismybatis plusmysqlRocketMQ 开发语言&#xff1a;java 开发工具&#xff1a;IDEA 前端框架&#xff1a;Uniapp 后端框架&#xff1a;springboot 数 据 库&#xff1a;mys…

【win11+vs 2017+OpenCV4.5.5+Qt5.12配置】解决了过程中遇到的小问题

0.版本选择 由于Qt5无法与最新的vs2022兼容&#xff0c;扩展工具中一直显示不可用&#xff0c;所以将vs降级成vs2017。 在安装Qt的过程中&#xff0c;会选择安装Qt套件&#xff0c;其中就的MCVS 2017&#xff0c;说明vs2017是与qt兼容的。 当然也可以用qt creator这一原生IDE。…

LEARNING TO EXPLORE USING ACTIVE NEURAL SLAM 论文阅读

论文信息 题目&#xff1a;LEARNING TO EXPLORE USING ACTIVE NEURAL SLAM 作者&#xff1a;Devendra Singh Chaplot, Dhiraj Gandhi 项目地址&#xff1a;https://devendrachaplot.github.io/projects/Neural-SLAM 代码地址&#xff1a;https://github.com/devendrachaplot/N…

ES6基础知识九:你是怎么理解ES6中Module的?使用场景?

一、介绍 模块&#xff0c;&#xff08;Module&#xff09;&#xff0c;是能够单独命名并独立地完成一定功能的程序语句的集合&#xff08;即程序代码和数据结构的集合体&#xff09;。 两个基本的特征&#xff1a;外部特征和内部特征 外部特征是指模块跟外部环境联系的接口…

Stable Diffusion AI绘画学习指南【插件安装设置】

插件安装的方式 可用列表方式安装&#xff0c;点开Extensions 选项卡&#xff0c;找到如下图&#xff0c;找到Available选项卡&#xff0c;点load from加载可用插件&#xff0c;在可用插件列表中找到要装的插件按install 按扭按装&#xff0c;安装完后(Apply and restart UI)应…

15、两个Runner初始化器和 springboot创建非web应用

两个Runner初始化器 两个Runner初始化器——主要作用是对component组件来执行初始化 这里的Component组件我理解为是被Component注解修饰的类 Component //用这个注解修饰的类&#xff0c;意味着这个类是spring容器中的一个组件&#xff0c;springboot应用会自动加载该组件。 …