当前位置: 首页 > news >正文

Java的NIO体系

目录

  • NIO
    • 1、操作系统级别下的IO模型有哪些?
    • 2、Java语言下的IO模型有哪些?
    • 3、Java的NIO应用场景?相比于IO的优势在哪?
    • 4、Java的IO、NIO、AIO 操作文件读写
    • 5、NIO的核心类 :Buffer(缓冲区)、Channel(通道)、Selector(选择器)
    • 6、Java NIO中的零拷贝优化支持

NIO

1、操作系统级别下的IO模型有哪些?

阻塞式 IO (Blocking IO):
当应用程序发起 IO 操作时,如果数据还没有准备好或者无法立即处理,IO 操作会阻塞程序的执行,直到数据准备就绪或者操作完成为止。

非阻塞式 IO (Non-blocking IO):
在非阻塞 IO 模型中,应用程序发起 IO 操作后,会立即返回,无论数据是否就绪或者能否立即处理。这样程序可以继续执行其他任务,而不必等待 IO 操作完成。需要通过轮询或者事件通知等方式来检查 IO 操作的状态。

IO 复用 (IO Multiplexing):
IO 复用模型通过操作系统提供的多路复用机制,如 select、poll 或 epoll,在一个线程中同时监视多个 IO 通道的状态。当其中任意一个 IO 通道就绪时,程序可以进行相应的处理。常见于网络编程中。

信号驱动 IO (Signal-driven IO):
在信号驱动 IO 模型中,应用程序会将 IO 操作请求发送给操作系统,并注册一个信号处理函数。当 IO 操作完成时,操作系统会发送一个信号给应用程序,通知其 IO 操作已完成,然后应用程序可以调用相应的处理函数来处理数据。

异步 IO (Asynchronous IO):
异步 IO 模型中,应用程序发起 IO 操作后立即返回,但是会指定一个回调函数或者事件处理器。当 IO 操作完成时,操作系统会通知应用程序,然后调用指定的回调函数来处理数据。相比非阻塞 IO,异步 IO 不需要程序通过轮询来检查 IO 状态,因此效率更高。

2、Java语言下的IO模型有哪些?

BIO (Blocking I/O)
BIO 属于同步阻塞 IO 模型,该模型中,应用程序发起IO操作后,会一直阻塞,直到操作系统内核把数据拷贝到用户空间。

NIO (Non-blocking I/O)
Java 中的 NIO 于 JDK 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。
它是支持面向缓冲的,基于通道的 I/O 操作方法。
NIO适用于 高负载、高并发的(网络)请求。
Java 中的 NIO 可以看作是 IO 复用模型

AIO (Asynchronous I/O)AIO
JDK 1.7 中引入
它是异步 IO 模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

3、Java的NIO应用场景?相比于IO的优势在哪?

多路复用:
NIO可以使用单个线程管理多个通道,这种多路复用的特性使得它非常适合处理大量的并发连接,例如网络服务器。
NIO提供了选择器(Selector)机制,可以通过一个线程管理多个通道的IO操作。当某个通道有IO事件发生时,可以通过选择器进行通知,从而实现高效的事件驱动模型。

非阻塞 I/O
NIO 支持非阻塞 I/O,这意味着在执行 I/O 操作时,线程不会被阻塞。这使得在网络传输中可以有效地管理大量并发连接。

缓冲
NIO 读写数据都是通过缓冲区进行操作的。读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。NIO利用 Buffer 和Channel可以高效的管理网络IO中的字节流数据。 这点类似于传统IO中的 BufferedInputStream中的缓冲区。

总的来说:
NIO性能优势主要体现在处理高并发的网络IO场景。
传统 I/O 在网络通信中主要使用阻塞式 I/O,为每个连接分配一个线程。当连接数量增加时,系统性能将受到严重影响,线程资源成为系统的性能瓶颈。而 NIO 提供了非阻塞 I/O 和 I/O 多路复用,可以在单个线程中处理多个并发连接,从而在网络传输中显著提高性能。

对于处理请求数较少或者少量连接读写大文件的场景其优势相对于传统IO并不明显。

4、Java的IO、NIO、AIO 操作文件读写

 public static void main(String[] args) {/*==========传统IO==========*/try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("123.txt"))){bufferedWriter.write("测试传统IO");} catch (IOException e) {e.printStackTrace();}try (BufferedReader bufferedReader = new BufferedReader(new FileReader("123.txt"));){String line;while ((line = bufferedReader.readLine()) != null){System.out.println(line);}} catch (Exception e) {e.printStackTrace();}/*==========NIO==========*/Path path = Paths.get("456.txt");try ( FileChannel fileChannel = FileChannel.open(path, EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE))) {ByteBuffer buffer = StandardCharsets.UTF_8.encode("测试NIO");fileChannel.write(buffer);} catch (IOException e) {e.printStackTrace();}try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)){ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = fileChannel.read(buffer);while (bytesRead != -1) {buffer.flip();System.out.println(StandardCharsets.UTF_8.decode(buffer));buffer.clear();bytesRead = fileChannel.read(buffer);}} catch (IOException e) {e.printStackTrace();}/*==========AIO==========*/// 使用 Paths.get() 获取文件路径Path pathAIO = Paths.get("789.txt");try {AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(pathAIO, StandardOpenOption.WRITE, StandardOpenOption.CREATE);ByteBuffer buffer = StandardCharsets.UTF_8.encode("测试AIO");Future<Integer> result = fileChannel.write(buffer, 0);result.get();fileChannel.close();} catch (Exception e) {e.printStackTrace();}try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(pathAIO, StandardOpenOption.READ)){ByteBuffer buffer = ByteBuffer.allocate(1024);// 异步读取 主线程会继续往下执行 为了防止读取完成之前 程序运行结束 使用线程同步器来处理同步问题CountDownLatch countDownLatch = new CountDownLatch(1);fileChannel.read(buffer, 0, buffer, new CompletionHandler<>() {@Overridepublic void completed(Integer result, ByteBuffer attachment) {attachment.flip();System.out.println(StandardCharsets.UTF_8.decode(attachment));attachment.clear();countDownLatch.countDown();}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {System.out.println("读取失败");exc.printStackTrace();countDownLatch.countDown();}});// 等待异步操作完成countDownLatch.await();} catch (Exception e) {e.printStackTrace();}}

5、NIO的核心类 :Buffer(缓冲区)、Channel(通道)、Selector(选择器)

在传统的BIO(Blocking IO)中

  • IO 是面向流的处理,比如 InputStream和 OutputStream,面向流的 I/O 系统一次一个字节地处理数据。

  • NIO(Non-blocking IO) 是面向块(缓冲区)的处理,面向块(缓冲区)的 I/O 系统以缓存块的形式处理数据。
    有点类似于BIO中的 缓冲流 BufferedInputStream和BufferedOutputStream

在NIO体系中是以 Buffer 缓冲区和 Channel 通道配合来处理数据的。

Buffer(缓冲区):
Buffer是抽象类:
其中最常用的之类是 ByteBuffer 字节缓冲区。
在这里插入图片描述

Buffer中维护了4个重要的变量用来描述缓冲区的功能特性。
在这里插入图片描述

  • capacity: 缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,不能被改变,不能为负数。
  • limit: 缓冲区的限制 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量。
    可以理解为Buffer 中可以读/写数据的边界。在写模式下,limit 代表最多能写入的数据,一般等于 capacity(可以通过limit(int newLimit)方法设置);读模式下,limit 等于 Buffer 中实际写入的数据大小。
  • position: 下一个要被读或写的元素的索引。position 会自动由相应的 get()和 put()函数更新。缓冲区的位置不能为负,并且不能大于其限制。 从写操作模式到读操作模式切换的时候(调用flip方法),position 都会归零,这样就可以从头开始读写了。
  • mark: 标记,Buffer允许将位置直接定位到该标记处,这是一个可选属性;

JDK文档中说明:标记、位置、限制和容量值遵守以下关系:
0 <= 标记 <= 位置 <= 限制 <= 容量

**Channel **
Channel 通道只负责传输数据、不直接操作数据。操作数据都是通过 Buffer 缓冲区来进行操作!通常,通道可以分为两大类:(FileChannel)文件通道和(SocketChannel)套接字通道。

BIO 中的流是单向的,分为各种 InputStream(输入流)和 OutputStream(输出流),数据只是在一个方向上传输(类似于通信信道的单工通信方式)。
通道与流的不同之处在于通道是双向的,它可以用于读、写或者同时用于读写(类似于通信信道的全双工通信方式)。

常用的Channel实现:

  • FileChannel:用于文件 I/O 的通道,支持文件的读、写和追加操作。FileChannel 允许在文件的任意位置进行数据传输,支持文件锁定以及内存映射(涉及零拷贝优化相关技术)文件等高级功能。FileChannel 无法设置为非阻塞模式,因此它只适用于阻塞式文件操作。

  • SocketChannel:用于 TCP 套接字 I/O 的通道。SocketChannel 支持非阻塞模式,可以与 Selector一起使用,实现高效的网络通信。SocketChannel 允许连接到远程主机,进行数据传输。

  • ServerSocketChannel:用于监听 TCP 套接字连接的通道。与 SocketChannel 类似,ServerSocketChannel 也支持非阻塞模式,并可以与 Selector 一起使用。ServerSocketChannel 负责监听新的连接请求,接收到连接请求后,可以创建一个新的 SocketChannel 以处理数据传输。

  • DatagramChannel:用于 UDP 套接字 I/O 的通道。DatagramChannel 支持非阻塞模式,可以发送和接收数据报包,适用于无连接的、不可靠的网络通信。

  • AsynchronousFileChannel:AsynchronousFileChannel 是 Java 7 引入的一个异步文件通道类,提供了对文件的异步读、写、打开和关闭等操作。

FileChannel的代码示例:

public static void main(String[] args) {try (FileChannel sourceChannel = FileChannel.open(Paths.get("C:\\123.txt"), StandardOpenOption.READ);FileChannel destinationChannel = FileChannel.open(Paths.get("C:\\123_copy.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {ByteBuffer buffer = ByteBuffer.allocate(8*1024);// 如果还有数据就循环读取写入 while (sourceChannel.read(buffer) != -1) {// 转换写模式buffer.flip();// 写入destinationChannel.write(buffer);// 写入后重置缓冲区buffer.clear();}}catch (Exception e){e.printStackTrace();}}

AsynchronousFileChannel的代码示例

public static void main(String[] args){Path path = Paths.get("123.txt");// 使用 Future 方式try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {ByteBuffer buffer = ByteBuffer.allocate(1024);long position = 0;while (true) {Future<Integer> result = fileChannel.read(buffer, position);int bytesRead = result.get();if (bytesRead <= 0) {break;}position += bytesRead;// 转换成读模式buffer.flip();byte[] data = new byte[buffer.limit()];buffer.get(data);System.out.println(new String(data));buffer.clear();}}catch (Exception e){e.printStackTrace();}// 实现 CompletionHandler 接口方式try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);){ByteBuffer buffer = ByteBuffer.allocate(1024);AtomicLong position = new AtomicLong(0);CountDownLatch latch = new CountDownLatch(1);fileChannel.read(buffer, position.get(), null, new CompletionHandler<Integer, Object>() {@Overridepublic void completed(Integer bytesRead, Object attachment) {if (bytesRead > 0) {position.addAndGet(bytesRead);buffer.flip();byte[] data = new byte[buffer.limit()];buffer.get(data);System.out.print(new String(data));buffer.clear();fileChannel.read(buffer, position.get(), attachment, this);} else {latch.countDown();}}@Overridepublic void failed(Throwable exc, Object attachment) {System.out.println("Error: " + exc.getMessage());latch.countDown();}});latch.await();} catch (Exception e) {e.printStackTrace();}}

Selector
Selector(选择器) 是基于事件驱动的 I/O 多路复用模型,它允许一个线程处理多个 Channel(这点非常重要)。
Selector 模型通过将 I/O 操作转化为事件驱动的方式,实现了高效的多路复用,来提高系统的并发处理能力和效率。

Selector的重要特性:

  • 事件注册: 在 Selector 模型中,程序会向 Selector 对象注册感兴趣的事件,这些事件可以是连接Socket建立、读数据、写数据等。
    也就是一个Selector 可以注册 多个Channel,我们只需要使用一个线程管理Selector就能够处理Selector 上的多个通道(Channel)。
    NIO处理高并发的关键所在。

  • 事件监听: Selector 会不断地轮询注册在其上的通道(Channel),检查这些通道是否有已经就绪的事件发生。

  • 事件处理: 当 Selector 发现某个通道上发生了感兴趣的事件时,它会通知程序,并且程序可以根据事件类型执行相应的操作。例如,如果一个通道的数据可读,程序可以读取数据并进行处理;如果一个通道的数据可写,程序可以将数据写入通道。

  • 非阻塞式调用: 在 Selector 模型中,通常会使用非阻塞式调用(Non-blocking I/O),这意味着程序可以在等待事件发生时继续执行其他任务,而不会被阻塞。

  • 多路复用: Selector 能够同时监听多个通道,当任何一个通道上发生感兴趣的事件时,Selector 都能及时地通知程序,因此能够有效地实现多路复用,提高系统的并发处理能力。
    在这里插入图片描述
    SelectionKey抽象类表示 Channel通道 在 Selector 中的注册信息以及与该通道相关联的状态和操作。

可以通过Selector 抽象类的 open方法 创建 Selector 实例:

try {Selector selector = Selector.open();} catch (IOException e) {e.printStackTrace();}

在一个Selector 实例中 有三种 SelectionKey 集合分别用来返回不同状态的 Channel通道信息:
分别对应Selector 中的三个方法:

  • keys方法: 返回的 Set<SelectionKey> 代表了注册在该 Selector 上的 Channel
    在这里插入图片描述
  • selectedKeys方法: 返回的 Set<SelectionKey> 代表了所有可通过 select() 方法获取的、需要进行 IO 处理的 Channel
    在这里插入图片描述
  • 已取消键集: 是已被取消但其通道尚未注销的键的集合。不可直接访问此集合。在下一次执行 select() 方法时,这些 Channel 对应的 SelectionKey 会被彻底删除

SelectionKey 中定义了四种事件注册类型:

public static final int OP_READ = 1;  
public static final int OP_WRITE = 4;
public static final int OP_CONNECT = 8; 
public static final int OP_ACCEPT = 16; OP_READ(值为1):表示通道已经准备好进行读取操作。当通道中有数据可读时,将触发该事件。
OP_WRITE(值为4):表示通道已经准备好进行写入操作。当通道可写入数据时,将触发该事件。
OP_CONNECT(值为8):表示通道已经完成连接操作。该事件通常在客户端套接字进行连接时使用。
OP_ACCEPT(值为16):表示服务器套接字已经准备好接受新的连接。该事件通常在服务器端套接字接受新连接时使用。
这些常量可以在 SelectionKeyinterestOps() 方法中使用,用于指定注册感兴趣的事件类型。当注册的事件发生时,将触发相应的操作。

Selector的简单代码示例:

通过 ServerSocketChannel 实现群聊功能
服务端


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 TestA {public static void main(String[] args) {try {// 创建ServerSocketChannel实例 接受客户端连接请求ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 设置为非阻塞模式// 当调用 accept() 方法时,如果没有新的连接请求到达,该方法将立即返回null,而不是阻塞等待新的连接。// 这样可以使服务器同时处理多个连接而不必为每个连接创建一个新的线程serverSocketChannel.configureBlocking(false);// 绑定 18848端口serverSocketChannel.socket().bind(new InetSocketAddress(18848));// 创建Selector实例Selector selector = Selector.open();// 注册ServerSocketChannel 到 Selector ,监听 OP_ACCEPT 事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// 通过死循环持续监听 Selector 内通道的事件while (true) {// 阻塞地监听通道是否有事件发生。如果有通道已经准备好的事件,则 select() 方法会返回已经就绪的通道数int readyCounts = selector.select();// 如果没有就绪状态的通道就继续下一轮循环if (readyCounts == 0) {continue;}// 获取 selectedKeys 需要进行 IO 处理的 ChannelSet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();// 如果有就绪的通道 就循环处理while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {// 处理连接事件ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel client = server.accept();client.configureBlocking(false);// 将客户端通道注册到 Selector 并监听 OP_READ 事件client.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {// 处理读事件SocketChannel client = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = client.read(buffer);if (bytesRead > 0) {buffer.flip();System.out.println(new String(buffer.array(), 0, bytesRead));// 将客户端通道注册到 Selector 并监听 OP_WRITE 事件client.register(selector, SelectionKey.OP_WRITE);} else if (bytesRead < 0) {// 客户端断开连接client.close();}} else if (key.isWritable()) {// 处理写事件SocketChannel client = (SocketChannel) key.channel();// 接收到客户端的数据后 反馈给客户端 发送成功ByteBuffer buffer = ByteBuffer.wrap("发送成功".getBytes());client.write(buffer);// 将客户端通道注册到 Selector 并监听 OP_READ 事件client.register(selector, SelectionKey.OP_READ);}keyIterator.remove();}}} catch (IOException e) {e.printStackTrace();}}
}

客户端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;public class TestB {private Selector selector;private SocketChannel socketChannel;private static final String HOST = "localhost";private static final int PORT = 18848;public TestB() {try {selector = Selector.open();socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("连接到" + HOST + ":" + PORT + "群聊了");} catch (IOException e) {e.printStackTrace();}}public void start() {new Thread(() -> {try {while (true) {if (selector.select() > 0) {for (SelectionKey key : selector.selectedKeys()) {selector.selectedKeys().remove(key);if (key.isReadable()) {readMessage();}}}}} catch (IOException e) {e.printStackTrace();}}).start();try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {sendMessage("我是TestB");String input;while ((input = reader.readLine()) != null) {sendMessage("TestB:"+input);}} catch (IOException e) {e.printStackTrace();}}private void sendMessage(String message) throws IOException {if (message != null && !message.trim().isEmpty()) {ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());socketChannel.write(buffer);}}private void readMessage() throws IOException {ByteBuffer buffer = ByteBuffer.allocate(1024);int read = socketChannel.read(buffer);if (read > 0) {buffer.flip();String msg = new String(buffer.array(), 0, read);System.out.println(msg);}}public static void main(String[] args) {new TestB().start();}
}

在这里插入图片描述

**java.nio.file.Files 类 **
Files 类是 Java NIO(New I/O)包的一部分,提供了对文件操作的高级支持,包括对文件通道、文件锁等的操作。
相比于BIO的 File 类 支持更复杂和更高级的文件操作。 JDK1.7引入。

下面介绍一些常用的方法:

①、判断文件是否存在

// Path 为路径相关的接口抽象 ,   
// LinkOption是一个枚举类型,用于指定在处理文件时如何处理符号链接(symbolic links)。
//符号链接是指向另一个文件或目录的特殊文件,类似于Unix系统中的软链接或Windows系统中的快捷方式。
// LinkOption提供了两个常量:
// NOFOLLOW_LINKS:表示在处理文件时不要跟踪符号链接。如果指定了此选项,在对文件进行操作时,将不会解析符号链接所指向的实际文件,而是直接操作符号链接本身。
// FOLLOW_LINKS:表示在处理文件时要跟踪符号链接。如果指定了此选项,在对文件进行操作时,会自动解析符号链接,然后操作符号链接所指向的实际文件。
public static boolean exists(Path path, LinkOption... options)
public static void main(String[] args) {Path path = Paths.get("D:\\123.txt");// 默认 FOLLOW_LINKSboolean exists = Files.exists(path);System.out.println(exists);}

②、创建文件

// FileAttribute是文件属性或目录属性的抽象  常用实现有 PosixFileAttributes 
//  例如可以使用 PosixFileAttributes 来创建具有特定权限的文件
public static Path createFile(Path path,FileAttribute<?>... attrs)throws IOException
public static void main(String[] args) {Path path = Paths.get("D:\\1234.txt");try {Files.createFile(path);} catch (IOException e) {e.printStackTrace();}}

③、创建目录

 public static void main(String[] args) {Path path = Paths.get("D:\\12345");try {Files.createDirectory(path);} catch (IOException e) {e.printStackTrace();}}

④、删除文件

  public static void main(String[] args) {Path path = Paths.get("D:\\1234.txt");try {Files.delete(path);} catch (IOException e) {e.printStackTrace();}}

⑤、复制文件

public static void main(String[] args) {Path source = Paths.get("D:\\123.txt");Path target = Paths.get("D:\\1234.txt");try {// StandardCopyOption 有三个属性// REPLACE_EXISTING:表示如果目标文件已经存在,则用源文件替换目标文件。//COPY_ATTRIBUTES:表示在复制文件时也复制文件的属性。这些属性包括文件的元数据,例如文件权限、最后修改时间等。如果不使用此选项,目标文件将会获得系统默认的属性。//ATOMIC_MOVE:表示使用原子性操作来移动文件。原子性操作是指在一个步骤内完成的操作,要么全部成功,要么全部失败,没有中间状态。这个选项通常用于将文件从一个位置原子性地移动到另一个位置,确保移动操作的完整性。Files.copy(source,target, StandardCopyOption.REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}

⑥、移动文件

public static void main(String[] args) {Path source = Paths.get("D:\\123.txt");Path target = Paths.get("D:\\1234.txt");try {Files.move(source,target, StandardCopyOption.REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}

⑦、读取文本文件

public static void main(String[] args) {Path path = Paths.get("D:\\1234.txt");try {List<String> list = Files.readAllLines(path, StandardCharsets.UTF_8);list.forEach(System.out::println);} catch (IOException e) {e.printStackTrace();}}

⑧、写入文本文件

public static void main(String[] args) {Path path = Paths.get("D:\\1234.txt");try {// 追加模式//            Files.write(path,list,StandardCharsets.UTF_8, StandardOpenOption.APPEND);List<String> list = Arrays.asList("123", "456");Files.write(path,list,StandardCharsets.UTF_8);} catch (IOException e) {e.printStackTrace();}}

⑨、遍历

public class TestC {public static void main(String[] args) {Path path = Paths.get("D:\\");try {Files.walkFileTree(path,new MyFileVisitor());} catch (IOException e) {e.printStackTrace();}}}class MyFileVisitor extends SimpleFileVisitor<Path> {@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {// 准备访问目录return super.preVisitDirectory(dir, attrs);}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {// 访问文件System.out.println(file);return super.visitFile(file, attrs);}@Overridepublic FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {// 访问文件失败return super.visitFileFailed(file, exc);}@Overridepublic FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {// 正在访问目录System.out.println(dir);return super.postVisitDirectory(dir, exc);}
}

⑩、利用重写visitFile方法查找文件

public class TestC {public static void main(String[] args) {// 查找D盘有没有 123.txt 文件Path path = Paths.get("D:\\");try {MyFileVisitor myFileVisitor = new MyFileVisitor("123.txt");Files.walkFileTree(path,myFileVisitor);System.out.println(myFileVisitor.searchFilePath);} catch (IOException e) {e.printStackTrace();}}}class MyFileVisitor extends SimpleFileVisitor<Path> {private String searchFileName;public Path searchFilePath;public MyFileVisitor() {}public MyFileVisitor(String searchFileName) {this.searchFileName = searchFileName;}@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {String fileName = file.getFileName().toString();if (fileName.equals(searchFileName)) {searchFilePath = file;// 如果找到了 就终止return FileVisitResult.TERMINATE;}// 如果没找到继续查找return FileVisitResult.CONTINUE;}
}

6、Java NIO中的零拷贝优化支持

零拷贝优化:
推荐看几篇博客大致了解下https://zhuanlan.zhihu.com/p/83398714
https://blog.csdn.net/a745233700/article/details/122660332?spm=1001.2014.3001.5506

零拷贝(Zero-copy)是一种计算机程序设计领域的优化技术,旨在减少数据在内存之间复制的次数,从而提升系统性能,特别是对于大量数据的传输尤为重要。在传统的数据处理流程中,数据从一个位置(如磁盘)读取到操作系统内核缓冲区,再从内核缓冲区复制到用户空间的应用程序缓冲区,然后在某些场景下(如网络传输),数据可能还需要从用户空间复制回内核空间的网络缓冲区以便发送。

零拷贝技术试图消除或最小化这些不必要的数据复制步骤,具体方法有以下几种:

  • 用户空间直接访问(User-Space Direct Access): 允许应用程序直接访问内核管理的内存,例如通过内存映射(mmap)文件,这样数据可以从磁盘直接加载到用户空间并用于网络传输,无需先复制到内核空间。

  • 写时复制(Copy-on-Write): 在数据实际被修改前,多个进程可以共享同一份数据的内存映射,只有当数据需要修改时才会创建数据的副本。

  • 共享内存(Shared Memory): 多个进程可以直接访问同一块内存区域,避免数据在进程间复制。

  • DMA(Direct Memory Access : 在硬件级别实现零拷贝,允许外围设备(如网卡)直接读写内存,而不需要CPU介入数据搬运,常用于高速网络传输。

通过这些技术,零拷贝能够显著减少CPU使用率,降低内存带宽消耗,提升数据处理速度,尤其是在高负载的网络服务器、数据库和文件系统中效果明显。

JDK中对于零拷贝的支持
MappedByteBuffer 和 FileChannel 的transferTo()/transferFrom()方法

MappedByteBuffer的使用
MappedByteBuffer 用于表示一个内存映射文件,即将文件的一部分或全部映射到内存中,以便通过直接操作内存来实现对文件的读写。这种方式可以提高文件 I/O 的性能,因为操作系统可以直接在内存和磁盘之间传输数据,无需通过 Java 应用程序进行额外的数据拷贝。

MappedByteBuffer 是 NIO 基于内存映射(mmap)这种零拷⻉⽅式的提供的⼀种实现,底层实际是调用了 Linux 内核的 mmap 系统调用。它可以将一个文件或者文件的一部分映射到内存中,形成一个虚拟内存文件,这样就可以直接操作内存中的数据,而不需要通过系统调用来读写文件。

 public static void main(String[] args) throws IOException {try (FileChannel sourceChannel = FileChannel.open(Paths.get("D:\\123.txt"), StandardOpenOption.READ);FileChannel destinationChannel = FileChannel.open(Paths.get("D:\\123.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.READ)) {long fileSize = sourceChannel.size();MappedByteBuffer sourceMappedBuffer = sourceChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);MappedByteBuffer destinationMappedBuffer = destinationChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileSize);for (int i = 0; i < fileSize; i++) {// 一个字节一个字节写byte b = sourceMappedBuffer.get(i);destinationMappedBuffer.put(i, b);}}}

transferTo()/transferFrom()方法
transferTo()/transferFrom()是 NIO 基于发送文件(sendfile)这种零拷贝方式的提供的一种实现,底层实际是调用了 Linux 内核的 sendfile系统调用。它可以直接将文件数据从磁盘发送到网络,而不需要经过用户空间的缓冲区。

public static void main(String[] args) {Path sourcePath = Paths.get("123.txt");Path destinationPath = Paths.get("123_copy.txt");try (FileChannel sourceChannel = FileChannel.open(sourcePath, StandardOpenOption.READ);FileChannel destinationChannel = FileChannel.open(destinationPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {long position = 0;long count = sourceChannel.size();// 循环传输,直到所有字节都被传输// 因为缓冲区大小限制、通道的限制、网络限制、文件系统限制、操作系统限制等因素可能会导致  transferTo 无法传输count 大小的数据while (position < count) {long transferred = sourceChannel.transferTo(position, count - position, destinationChannel);position += transferred;}} catch (IOException e) {e.printStackTrace();}}

相关文章:

Java的NIO体系

目录 NIO1、操作系统级别下的IO模型有哪些&#xff1f;2、Java语言下的IO模型有哪些&#xff1f;3、Java的NIO应用场景&#xff1f;相比于IO的优势在哪&#xff1f;4、Java的IO、NIO、AIO 操作文件读写5、NIO的核心类 :Buffer&#xff08;缓冲区&#xff09;、Channel&#xff…...

自下而上的选股与自上而下的选股

一起学习了《战胜华尔街》&#xff0c;不知道大家有没有这么一种感受&#xff1a;林奇的选股方法是典型的自下而上的选股方法。虽然这一点没有单独拎出来讨论过&#xff0c;但在《从低迷中寻找卓越》《如何通过财务指标筛选股票&#xff1f;》《边逛街边选股&#xff1f;》《好…...

Tech Talk:智能电视eMMC存储的五问五答

智能电视作为搭载操作系统的综合影音载体&#xff0c;以稳步扩大的市场规模走入越来越多的家庭&#xff0c;成为人们生活娱乐的重要组成部分。存储部件是智能电视不可或缺的组成部分&#xff0c;用于保存操作系统、应用程序、多媒体文件和用户数据等信息。智能电视使用eMMC作为…...

scikit-learn教程

scikit-learn&#xff08;通常简称为sklearn&#xff09;是Python中最受欢迎的机器学习库之一&#xff0c;它提供了各种监督和非监督学习算法的实现。下面是一个基本的教程&#xff0c;涵盖如何使用sklearn进行数据预处理、模型训练和评估。 1. 安装和导入包 首先确保安装了…...

CentOS 7 搭建rsyslog日志服务器

CentOS 7 搭建rsyslog日志服务器 前言一、IP地址及主机名称规划1.修改主机名 二、配置rsyslog日志服务器1.安装rsyslog服务2.编辑/etc/rsyslog.conf 文件3.启动并启用rsyslog服务4.验证端口是否侦听 三、在rsyslog日志服务器上配置firewalld防火墙四、配置rsyslog日志客户端1.编…...

使用Spring Boot Actuator监控应用健康状态

使用Spring Boot Actuator监控应用健康状态 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将探讨如何利用Spring Boot Actuator来监控和管理应用程序的…...

leetcode刷题:vector刷题

​ ​ &#x1f525;个人主页&#xff1a;guoguoqiang. &#x1f525;专栏&#xff1a;leetcode刷题 1.只出现一次的数字 这道题很简单&#xff0c;我们只需要遍历一次数组即可通过异或运算实现。(一个数与自身异或结果为0&#xff0c;任何数与0异或还是它本身) class Solut…...

CGI面试题及参考答案

什么是CGI?它在Web服务器与应用程序之间扮演什么角色? CGI(Common Gateway Interface) 是一种标准协议,它定义了Web服务器与运行在服务器上的外部程序(通常是脚本或应用程序)之间的通信方式。简单来说,CGI充当了一个桥梁,使得Web服务器能够将用户的请求传递给后端程序…...

论文调研_物联网漏洞检测综述

A Review of IoT Firmware Vulnerabilities and Auditing Techniques 研究背景&#xff1a;物联网设备在工业、消费类等各个领域得到了广泛应用&#xff0c;实现了更高的自动化和生产率。然而&#xff0c;这些连网设备的高度依赖也带来了一系列网络安全威胁&#xff0c;特别是…...

Java学习【IO流:深入理解与应用(上)】

Java学习【IO流&#xff1a;深入理解与应用&#xff08;上&#xff09;】 &#x1f343;1.IO流体系结构&#x1f343;2.FileOutputStream&#x1f341;2.1FileOutputStream写数据的三种方式&#x1f341;2.2换行和续写 &#x1f343;3.FileInputStream&#x1f341;3.1每次读取…...

干货系列:SpringBoot3第三方接口调用10种方式

环境&#xff1a;SpringBoot.3.3.0 1、简介 在项目中调用第三方接口是日常开发中非常常见的。调用方式的选择通常遵循公司既定的技术栈和架构规范&#xff0c;以确保项目的一致性和可维护性。无论是RESTful API调用、Feign声明式HTTP客户端、Apache HttpClient等调用方式&…...

KVM性能优化之CPU优化

1、查看kvm虚拟机vCPU的QEMU线程 ps -eLo ruser,pid,ppid,lwp,psr,args |awk /^qemu/{print $1,$2,$3,$4,$5,$6,$8} 注:vcpu是不同的线程&#xff0c;而不同的线程是跑在不同的cpu上&#xff0c;一般情况&#xff0c;虚拟机在运行时自身会点用3个cpus&#xff0c;为保证生产环…...

lua中判断2个表是否相等

当我们获取 table 长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数&#xff0c;而导致无法正确取得 table 的长度&#xff0c;而且还会出现奇怪的现象。例如&#xff1a;t里面有3个元素&#xff0c;但是因为最后一个下表是5和4&#xff0c;却表现出不一…...

uni-app 自定义支付密码键盘

1.新建组件 payKeyboard .vue <template><view class"page-total" v-show"isShow"><view class"key-list"><view class"list" v-for"(item,index) in keyList" :class"{special:item.keyCode190…...

抖音微短剧小程序源码搭建:实现巨量广告数据高效回传

在数字化营销日益盛行的今天&#xff0c;抖音微短剧小程序已成为品牌与观众互动的新渠道。这些短小精悍的剧目不仅能迅速抓住用户的注意力&#xff0c;还能有效提升品牌的知名度和用户黏性。然而&#xff0c;想要充分利用这一营销工具&#xff0c;关键在于如何高效地追踪广告数…...

springboot数字化医院产科系统源码

目录 一、系统概述 二、开发环境 三、功能设计 四、功能介绍 一、系统概述 数字化产科是为医院产科量身定制的信息管理系统。它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。该系统由门诊系统、住院系统、数据统计模块三部分组成&#xff0c;与医院HIS、LI…...

uniapp微信接口回调 response.sendRedirect nginx 报404错误

如题 参考 uniapp打包H5时,访问index.html页面白屏报错net::ERR_ABORTED 404 - 简书 nginx中修改 配置文件 location / { try_files $uri $uri/ /index.html; root html; index index.html index.htm; } uniapp里配置 重新载入...

Python系统教程02

巩固 input()输出函数 回顾 1 、 input()函数&#xff1a; 在 input()函数输入时&#xff0c;输入的内容一定为字符串类型。 2 、条件分支语句&#xff1a; 每一个 if 语句可以看成一个个体&#xff0c;elif 和 else 都是一个 if 个体的一部分&#xff0c;每一个 if 个体 运…...

JS面试题6——深拷贝和浅拷贝

它们都是用来复制的 1. 浅拷贝&#xff08;只复制引用&#xff0c;而未复制真正的值&#xff09; /* 简单赋值 */ var arr1 [a, b, c, d]; var arr2 arr1; /* Object.assign实现的也是浅拷贝 */ var obj1 {a:1, b:2} var obj2 Object.assign(obj1); 2. 深拷贝&#xff08;是…...

Scrapy实现关键词搜索的数据爬取

爬虫技术对于从互联网上获取数据和信息非常重要&#xff0c;而scrapy作为一款高效、灵活和可扩展的网络爬虫框架&#xff0c;能够简化数据爬取的过程&#xff0c;对于从互联网上爬取数据的工作非常实用。本文将介绍如何使用scrapy实现关键词搜索的数据爬取。 Scrapy的介绍 Sc…...

java_网络服务相关_gateway_nacos_feign区别联系

1. spring-cloud-starter-gateway 作用&#xff1a;作为微服务架构的网关&#xff0c;统一入口&#xff0c;处理所有外部请求。 核心能力&#xff1a; 路由转发&#xff08;基于路径、服务名等&#xff09;过滤器&#xff08;鉴权、限流、日志、Header 处理&#xff09;支持负…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

IGP(Interior Gateway Protocol,内部网关协议)

IGP&#xff08;Interior Gateway Protocol&#xff0c;内部网关协议&#xff09; 是一种用于在一个自治系统&#xff08;AS&#xff09;内部传递路由信息的路由协议&#xff0c;主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

Frozen-Flask :将 Flask 应用“冻结”为静态文件

Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是&#xff1a;将一个 Flask Web 应用生成成纯静态 HTML 文件&#xff0c;从而可以部署到静态网站托管服务上&#xff0c;如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

NPOI操作EXCEL文件 ——CAD C# 二次开发

缺点:dll.版本容易加载错误。CAD加载插件时&#xff0c;没有加载所有类库。插件运行过程中用到某个类库&#xff0c;会从CAD的安装目录找&#xff0c;找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库&#xff0c;就用插件程序加载进…...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...