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();}}

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

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

相关文章

【proteus 51单片机入门】8*8led点阵

文章目录 前言如何点亮led点阵仿真图代码点亮led核心代码解析 爱心代码 滚动总结 前言 在嵌入式系统的开发中&#xff0c;LED点阵显示器是一种常见的显示设备&#xff0c;它可以用来显示各种图形和文字&#xff0c;为用户提供直观的信息反馈。本文将介绍如何使用Proteus软件和…

使用 MongoDB 剖析开放银行:技术挑战和解决方案

开放银行&#xff08;或开放金融&#xff09;在银行业掀起了一股颠覆性浪潮&#xff0c;它迫使金融机构&#xff08;银行、保险公司、金融科技公司、企业甚至政府机构&#xff09;迎接一个透明、协作和创新的新时代。这种模式转变要求银行与第三方提供商&#xff08;TPP&#x…

如何在 SQL 中删除一条记录?

如何在 SQL 中删除一条记录&#xff1f; 在 SQL 中&#xff0c;您可以使用DELETE查询和WHERE子句删除表中的一条记录。在本文中&#xff0c;我将向您介绍如何使用DELETE查询和WHERE子句删除记录。我还将向您展示如何一次从表中删除多条记录 如何在 SQL 中使用 DELETE 这是使…

【计算机图形学】期末考试知识点汇总(上)

文章目录 视频教程第一章 计算机图形学概述计算机图形学的定义计算机图形学的应用计算机图形学 vs 图像处理 vs模式识别图形显示器的发展及工作原理理解三维渲染管线 第二章 基本图元的扫描转换扫描转换直线的扫描转换DDA算法Bresenham算法中点画线算法圆的扫描转换中点画圆算法…

面试-J.U.C包的梳理

1.J.U.C包的梳理 Java.Util.Concurrent包简称JUC (1)JUC整体架构图 (2)分析 Executor&#xff1a;线程执行器&#xff0c;任务执行和调度的框架。Tools下存在executor相关的executors类&#xff0c;用于创建executorservice&#xff0c;scheduleexecutorservice&#xff0c;…

哪吒汽车,正在等待“太乙真人”的拯救

文丨刘俊宏 在360创始人、哪吒汽车股东周鸿祎近日连续且着急的“督战”中&#xff0c;哪吒汽车&#xff08;下简称哪吒&#xff09;终究还是顶不住了。 6月26日&#xff0c;哪吒通过母公司合众新能源在港交所提交了IPO文件&#xff0c;急迫地希望成为第五家登陆港股的造车新势力…

高精度除法的实现

高精度除法与高精度加法的定义、前置过程都是大致相同的&#xff0c;如果想了解具体内容&#xff0c;可以移步至我的这篇博客&#xff1a;高精度加法计算的实现 在这里就不再详细讲解&#xff0c;只讲解主体过程qwq 主体过程 高精度除法的原理和小学学习的竖式除法是一样的。 …

【Sklearn-驯化】一文搞懂机器学习树模型建模可视化过程

【Sklearn-驯化】一文搞懂机器学习树模型建模可视化过程 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&#xff…

OpenCV学习之cv2.imshow()函数

OpenCV学习之cv2.imshow()函数 一、简介 cv2.imshow 是 OpenCV 库中用于显示图像的基本函数之一。在图像处理和计算机视觉的过程中&#xff0c;使用该函数可以快速预览处理后的图像&#xff0c;便于调试和结果展示。 二、基本语法 cv2.imshow(WindowName, Imgmat)三、参数说…

如何制作鼠标悬浮后伸缩的搜索框

引言 许多博客都在使用的伸缩搜索框制作教程 成品展示&#xff08;颜色自行搭配&#xff09; 初步布局 居中盒子&&初始化样式 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewpo…

2SK241 LTSpice模型及仿真

2SK241是东芝生产的一款NMOS&#xff0c;早已停产&#xff0c;但是在收音机圈子里还是有很多死忠粉&#xff0c;所以在淘宝上也是一堆打磨改标的假货。 言归正传&#xff0c;在矿坛上找到了2SK241的模型&#xff1a; .model M2SK241bottom NMOS(Level1 Rd1 Rs10 Rg50 Kp8mV…

在高并发场景下,怎样避免 PostgreSQL 的死锁问题?

文章目录 &#xff08;一&#xff09;不当的事务设计&#xff08;二&#xff09;不正确的锁使用&#xff08;三&#xff09;并发操作冲突&#xff08;一&#xff09;优化事务设计&#xff08;二&#xff09;正确使用锁&#xff08;三&#xff09;调整数据库参数&#xff08;四&…

mindspore打卡第9天 transformer的encoder和decoder部分

mindspore打卡第9天 transformer的encoder和decoder部分 import mindspore from mindspore import nn from mindspore import ops from mindspore import Tensor from mindspore import dtype as mstypeclass ScaledDotProductAttention(nn.Cell):def __init__(self, dropout_…

2024年6月29日 (周六) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 喜马拉雅下载工具: 字面意思 《星刃》性感女主私密部位细节逼真 让玩家感到惊讶《星刃…

clion ctrl+左键只能跳转到虚函数的声明处

右击函数 -> GOTO -> Definition 这样不够便捷&#xff0c;但是我没有找到更好的办法 可能是因为该函数是虚函数的重写&#xff0c;clion 无法识别出该函数是虚函数的哪个重写版&#xff0c;只能跳转到唯一的虚函数位置

springboot注解@ComponentScan注解作用

一 ComponentScan作用 1.1 注解作用 项目会默认扫描SpringBootApplication注解所在路径的同级和下级的所有子包&#xff0c;使用ComponentScan后他会取代掉默认扫描。 ComponentScan 是Spring框架的注解&#xff0c;它的作用是扫描指定的包路径下的标有 Component、Service、…

力扣300. 最长递增子序列(动态规划)

Problem: 300. 最长递增子序列 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 明确题目涉及到求取最值问题因此我们可以考虑使用动态规划来解决问题 1.定义状态&#xff1a;定义int类型的dp数组表示以nums[i]结尾的序列的最长长度&#xff0c;初始化均为1即表示…

Python | Leetcode Python题解之第198题打家劫舍

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:if not nums:return 0size len(nums)if size 1:return nums[0]first, second nums[0], max(nums[0], nums[1])for i in range(2, size):first, second second, max(first nu…

LNBxx21的功能

LNBxx21功能&#xff1a; LNBxx21 家族是为卫星LNB模块供电/连接LNB块与接收机的集成化解决方案。LNBxx21的很多功能可以让LNB电源/接口符合国际标准&#xff0c;此外&#xff0c;模块内还包括一个I2C总线接口&#xff0c;因为集成了一个升压直流-直流控制器&#xff0c;所以可…

MySQL高级-InnoDB引擎-事务日志- redo log(事务持久性的保证)

文章目录 1、redo log1.1、重做日志缓冲&#xff08;redo log buffer&#xff09;1.2、重做日志文件&#xff08;redo log file&#xff09; 2、如果没有redo log&#xff0c;可能会存在什么问题的&#xff1f;2.2、我们一起来分析一下。 2.2、那么&#xff0c;如何解决上述的问…