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.rhkb.cn/news/368126.html

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

相关文章

【代码随想录——图论——图论理论基础】

1. 图论理论基础 1.1 图的基本概念 二维坐标中&#xff0c;两点可以连成线&#xff0c;多个点连成的线就构成了图。 当然图也可以就一个节点&#xff0c;甚至没有节点&#xff08;空图&#xff09; 1.1.1 图的种类 有向图 加权有向图无权有向图 无向图 加权无向图无权无向…

.NET C# 使用OpenCV实现人脸识别

.NET C# 使用OpenCV实现模型训练、人脸识别 码图~~~ 1 引入依赖 OpenCvSHarp4 - 4.10.0.20240616 OpenCvSHarp4.runtime.win - 4.10.0.20240616 2 人脸数据存储结构 runtime directory | face | {id}_{name} | *.jpg id - 不可重复 name - 人名 *.jpg - 人脸照片3 Demo 3.…

松下Panasonic机器人维修故障原因

松下机器人伺服电机是许多工业自动化设备的关键组成部分。了解如何进行Panasonic工业机械臂电机维修&#xff0c;对于确保设备正常运行至关重要。 【松下焊接机器人维修案例】【松下机器人维修故障排查】 一、常见松下工业机械手伺服电机故障及原因 1. 过热&#xff1a;过热可…

vue2+element-ui新增编辑表格+删除行

实现效果&#xff1a; 代码实现 &#xff1a; <el-table :data"dataForm.updateData"border:header-cell-style"{text-align:center}":cell-style"{text-align:center}"><el-table-column label"选项字段"align"center&…

C++:求梯形面积

梯形面积 已知上底15厘米&#xff0c;下底25厘米&#xff0c;问梯形面积值是多少&#xff1f; #include<iostream> using namespace std; int main() {//梯形的面积公式&#xff08;上底下底&#xff09; 高 2//上底变量、下底变量int s,d,h,m;s15;d25;h 2*150 * 2/s ;…

相亲交友APP系统婚恋交友社交软件开发语音视频聊天平台定制开发-婚恋相亲交友软件平台介绍——app小程序开发定制

互联网飞速发展的时代&#xff0c;相亲交友软件成为了许多年轻人首选的相亲方式&#xff0c;越来越多的单身男女希望在婚恋交友软件平台上寻找灵魂伴侣&#xff0c;相亲交友软件因此具有很高的市场价值。 多客婚恋相亲交友系统是一款定位高端&#xff0c;到手就能运营的成熟婚恋…

Redis 管道(Pipeline)是什么?有什么用?

目录 1. redis 客户端-服务端模型的不足之处 2. redis 管道是什么&#xff1f;有什么好处&#xff1f; 3. 管道的使用场景 4. 管道使用的注意事项 1. redis 客户端-服务端模型的不足之处 众所周知&#xff0c;redis 是一个客户端-服务端的模型设计&#xff0c;客户端向服务…

SALOME源码分析:View Model

作为一款开源的CAx(CAD/CAE/CAM)软件集成平台&#xff0c;为了实现各个Module支持不同的数据显示与交互方案&#xff0c;出于扩展性的考虑&#xff0c;SALOME引入了View Model&#xff0c;用以支持OpenGL、OCC、VTK、ParaView、Qwt等数据显示与交互实现。 本文将以OCCViewer、…

一文搞懂 java 线程池:ScheduledThreadPool 和 WorkStealingPool 原理

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

PHP宜邦家政服务管理系统-计算机毕业设计源码04426

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2开发现状 1.3论文结构与章节安排 2 宜邦家政服务管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用…

力扣hot100 -- 动态规划(上)

目录 ❄技巧 &#x1f33c;爬楼梯 &#x1f354;杨辉三角 &#x1f30a;打家劫舍 &#x1f40e;完全平方数 &#x1f33c;零钱兑换 &#x1f33c;单词拆分 ❄技巧 动态规划dp-CSDN博客 &#x1f446;花 5 分钟快速刷一遍 花 10 分钟浏览一下 线性DP 背包DP&#x1f447…

VS展示6个错误中的0个解决方法

左键点击展示6个错误中的0个 左键点击展示23个警告中的0个

国产强大免费WAF, 社区版雷池动态防护介绍

雷池WAF&#xff0c;基于智能语义分析的下一代 Web 应用防火墙 使用情况 我司于2023年4月23日对雷池进行测试&#xff0c;测试一个月后&#xff0c;于2023年5月24日对雷池进行正式切换&#xff0c;此时版本为1.5.1。 里程碑纪念 后续一直跟随雷池进行版本升级&#xff0c;当前…

怎样使用js技术实现Chrome投屏功能?

在Web前端技术中&#xff0c;直接控制浏览器窗口或标签页从主屏投屏到副屏&#xff08;如PPT的演讲者模式&#xff09;并不简单&#xff0c;而且直接控制浏览器窗口从主屏投屏到副屏的功能超出了Web标准的范畴&#xff0c;并且涉及到用户系统级别的设置和权限&#xff0c;因此不…

ETCD概述--使用/特性/架构/原理

ETCD概述 ETCD是一个高度一致的分布式键值存储, 它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据(高可用, 强一致性)​全局的配置服务中心. 本文将介绍其特性、相关操作和常见的应用场景. 如果想了解更多, 请查阅我的技术博客: https://dingyuqi.com 特性 …

C语言分支和循环(下)

C语言分支和循环&#xff08;下&#xff09; 1. 随机数生成1.1 rand1.2 srand1.3 time1.4 设置随机数的范围 2. 猜数字游戏实现 掌握了前面学习的这些知识&#xff0c;我们就可以写⼀些稍微有趣的代码了&#xff0c;比如&#xff1a; 写⼀个猜数字游戏 游戏要求&#xff1a; 电…

git常用命令速查表

Git相关概念简述 版本库&#xff1a;git在本地开辟的一个存储空间&#xff0c;一般在 .git 文件里。工作区(workspace)&#xff1a; 就是编辑器里面的代码&#xff0c;我们平常开发直接操作的就是工作区。暂存区&#xff08;index/stage&#xff09;&#xff1a;暂时存放文件的…

13. Revit API: Filter(过滤器)

13. Revit API: Filter&#xff08;过滤器&#xff09; 前言 在讲Selection之前&#xff0c;还是有必要先了解一下的过滤器的。 对了&#xff0c;关于查找一些比较偏的功能或者API的用法&#xff0c;可以这样查找 关键词 site:https://thebuildingcoder.typepad.com/ site是…

C语言 -- 函数

C语言 -- 函数 1. 函数的概念2. 库函数2.1 标准库和头文件2.2 库函数的使用方法2.2.1 功能2.2.2 头文件包含2.2.3 实践2.2.4 库函数文档的一般格式 3. 自定义函数3.1 函数的语法形式3.2 函数的举例 4. 形参和实参4.1 实参4.2 形参4.3 实参和形参的关系 5. return 语句6. 数组做…

react ts 封装3D柱状图,支持渐变

留档&#xff0c;以防忘记 bar3D.tsx import React, { useEffect, useRef, useState } from react; import * as echarts from echarts; import echarts/lib/chart/bar; import echarts/lib/chart/pictorialBar; import echarts/lib/component/grid; import echarts/lib/comp…