目录

  • 一、NIO 与 IO 的深度剖析
    • 1.1 IO 的局限性
    • 1.2 NIO 核心特性
    • 1.3 NIO 核心组件
    • 1.4 NIO 适用场景
  • 二、NIO 核心组件实战
    • 2.1 Buffer 缓冲区
    • 2.2 Channel 通道
    • 2.3 Selector 选择器
    • 2.4 NIO 文件操作案例
  • 三、NIO2.0 实战
    • 3.1 Path 类
    • 3.2 Files 类
    • 3.3 Files 类高级操作
    • 3.4 NIO2.0 实战案例


一、NIO 与 IO 的深度剖析

在 Java 编程领域,输入输出(I/O)操作是与外部资源交互的基础,如文件、网络连接等。传统的 I/O 模型在处理简单场景时表现出色,但随着应用程序对性能和并发处理能力要求的不断提高,其局限性逐渐显现。Java NIO(New I/O)的出现,为开发者提供了一种更高效、更灵活的 I/O 处理方式,尤其在高并发和大数据传输场景中展现出显著优势。接下来,我们将深入探讨 NIO 与传统 IO 的区别,以及 NIO 的核心特性、组件和适用场景。

1.1 IO 的局限性

传统的 Java IO 是基于流(Stream)的操作,其主要特点是面向字节或字符序列,数据以顺序的方式从数据源读取或写入到目的地。这种方式在处理简单的 I/O 任务时非常直观和方便,但在高并发和大数据量处理场景下,暴露出了一些明显的局限性。

  • 阻塞问题:Java IO 是阻塞式的,当一个线程调用 read () 或 write () 方法时,该线程会被阻塞,直到有数据可读或数据完全写入。这意味着在 I/O 操作进行期间,线程无法执行其他任务,严重浪费了 CPU 资源。例如,在一个服务器应用中,如果同时有大量客户端连接,每个连接都需要一个独立的线程来处理 I/O 操作,那么随着连接数的增加,线程数量也会急剧增加,导致线程上下文切换开销增大,系统性能急剧下降。
  • 面向流的局限性:IO 是面向流的,流是单向的,要么是输入流,要么是输出流。这就限制了数据处理的灵活性,对于一些需要同时进行读写操作的场景,需要分别创建输入流和输出流,增加了代码的复杂性。此外,流操作是顺序的,无法随机访问数据,对于一些需要随机读写的场景,如文件的部分内容更新,处理起来比较困难。
  • 单线程处理能力有限:由于每个 I/O 操作都需要一个线程来处理,当并发连接数较多时,线程资源会被大量消耗,系统的可扩展性受到限制。而且,线程的创建和销毁也会带来一定的开销,进一步降低了系统的性能。

1.2 NIO 核心特性

Java NIO 旨在解决传统 IO 的局限性,提供了一系列新的特性,使其在高并发和大数据处理场景中表现出色。

  • 非阻塞特性:NIO 支持非阻塞 I/O 操作,当一个线程从通道请求数据时,如果没有数据可用,该线程不会被阻塞,而是立即返回。这使得一个线程可以管理多个通道,大大提高了系统的并发处理能力。例如,在一个网络服务器中,可以使用一个线程来监听多个客户端连接的事件,当有事件发生时,才对相应的连接进行处理,避免了线程的阻塞等待,提高了资源利用率。
  • 面向缓冲区:NIO 基于缓冲区(Buffer)进行数据操作,所有数据都要先写入缓冲区,然后再从缓冲区读取。缓冲区是一块连续的内存区域,提供了更灵活的数据处理方式,可以随机访问数据,并且支持数据的读写、标记、重置等操作。与面向流的 IO 相比,面向缓冲区的 NIO 在处理大数据量时效率更高,因为它减少了数据的拷贝次数。
  • 选择器:选择器(Selector)是 NIO 的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。通过选择器,可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。例如,在一个网络服务器中,可以将所有的客户端连接通道注册到一个选择器上,选择器不断轮询这些通道,当有通道准备好进行 I/O 操作时,选择器就会通知相应的线程进行处理,避免了每个连接都需要一个线程来处理的情况,节省了系统资源。

1.3 NIO 核心组件

NIO 的核心组件包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),它们协同工作,实现了高效的 I/O 操作。

  • Channel(通道):通道是连接数据源或目的地的双向通道,可以进行读、写或同时进行读写操作。与传统的流不同,通道支持异步操作,并且可以与多个缓冲区进行交互。常见的通道类型有 FileChannel(用于文件的读写操作)、SocketChannel(用于通过 TCP 协议进行网络通信)、ServerSocketChannel(用于监听客户端的连接请求)和 DatagramChannel(用于通过 UDP 协议进行网络通信)。例如,通过 FileChannel 可以实现对文件的高效读写,支持随机访问和内存映射等高级操作;通过 SocketChannel 可以实现非阻塞的网络通信,提高网络应用的并发性能。
  • Buffer(缓冲区):缓冲区是 NIO 中数据的容器,用于存储数据。所有数据的读写都要通过缓冲区进行,缓冲区提供了对数据的结构化访问以及维护读写位置等信息。常用的缓冲区类型有 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer 等,分别用于存储不同类型的数据。在使用缓冲区时,通常需要经历分配 Buffer、写入数据到 Buffer、切换 Buffer 为读模式和从 Buffer 中读取数据等步骤。例如,通过 ByteBuffer 的 allocate () 方法可以分配一个指定容量的缓冲区,然后使用 put () 方法将数据写入缓冲区,再通过 flip () 方法将缓冲区从写模式切换为读模式,最后使用 get () 方法从缓冲区中读取数据。
  • Selector(选择器):选择器用于监听多个通道的事件,如连接打开、数据到达等。一个选择器可以注册多个通道,当其中的某些通道上有感兴趣的事件发生时,这些通道就会变为可用状态,可以在选择器的选择操作中被选中。通过选择器,一个线程可以管理多个通道,实现非阻塞的 I/O 操作,提高系统的并发处理能力。使用选择器的基本流程包括创建 Selector、将通道注册到 Selector 上并指定感兴趣的事件类型,以及不断循环地调用 Selector 的 select () 方法来检查是否有通道已经准备好进行 I/O 操作,最后处理准备就绪的通道。

1.4 NIO 适用场景

NIO 的特性使其在以下场景中具有明显的优势:

  • 高并发网络应用:在开发高性能的网络服务器或客户端时,NIO 可以处理大量并发连接,通过非阻塞 I/O 和选择器机制,提高系统的并发处理能力和资源利用率。例如,常见的网络通信框架如 Netty、Mina 等,都基于 NIO 实现,能够支持海量的并发连接,广泛应用于互联网、游戏、金融等领域。
  • 大数据传输:NIO 提供的通道和缓冲区概念,可以高效地进行大规模数据的传输。在处理大文件读写时,通过 FileChannel 和 ByteBuffer 的配合,可以减少 I/O 操作的次数,提高数据传输的效率。例如,在进行文件的拷贝、备份等操作时,使用 NIO 可以大大缩短操作时间。
  • 需要高效利用系统资源的场景:由于 NIO 可以使用较少的线程来处理大量的 I/O 操作,减少了线程上下文切换的开销,因此在对系统资源利用率要求较高的场景中,NIO 是更好的选择。例如,在一些嵌入式系统或资源受限的环境中,使用 NIO 可以在有限的资源条件下实现高效的 I/O 处理。

二、NIO 核心组件实战

了解了 NIO 的基本概念和原理后,接下来我们通过实际的代码示例来深入学习 NIO 的核心组件 —— 缓冲区(Buffer)、通道(Channel)和选择器(Selector)的使用。

2.1 Buffer 缓冲区

在 NIO 中,Buffer 是一个用于存储数据的容器,所有数据的读写都要通过缓冲区进行。常见的缓冲区类型有 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer 等,分别用于存储不同类型的数据。下面以 ByteBuffer 为例,介绍缓冲区的创建、读写操作以及 flip 和 clear 方法的使用。

创建缓冲区
可以使用静态方法 allocate 来创建一个指定容量的缓冲区。例如,创建一个容量为 1024 字节的 ByteBuffer:

ByteBuffer buffer = ByteBuffer.allocate(1024);

也可以通过 wrap 方法将一个现有的数组包装成缓冲区:

byte[] array = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap(array);

写入数据

使用 put 方法将数据写入缓冲区。例如,将一个字符串写入 ByteBuffer:

String message = "Hello, NIO!";
buffer.put(message.getBytes());

也可以从通道中读取数据到缓冲区,假设我们有一个 FileChannel:

FileInputStream fis = new FileInputStream("example.txt");
FileChannel channel = fis.getChannel();
channel.read(buffer);

读取数据

在读取数据之前,需要先调用 flip 方法将缓冲区从写模式切换为读模式。flip 方法会将 position 设置为 0,并将 limit 设置为当前 position 的值,这样就可以从缓冲区的开头开始读取数据了。例如:

buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String result = new String(data);
System.out.println(result);

flip 与 clear 方法

  • flip 方法:如上述所说,用于将缓冲区从写模式切换为读模式。
  • clear 方法:用于清空缓冲区,将 position 设置为 0,limit 设置为容量大小。但需要注意的是,clear 方法并不会真正删除缓冲区中的数据,只是重置了缓冲区的状态,以便重新写入数据。例如:
buffer.clear();

通过以下完整代码示例,可以更清晰地看到缓冲区的工作原理:

import java.nio.ByteBuffer;public class BufferExample {public static void main(String[] args) {// 创建缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 写入数据String message = "Hello, NIO!";buffer.put(message.getBytes());// 切换为读模式buffer.flip();// 读取数据byte[] data = new byte[buffer.remaining()];buffer.get(data);String result = new String(data);System.out.println(result);// 清空缓冲区buffer.clear();}
}

2.2 Channel 通道

Channel 是 NIO 中用于与数据源或目的地进行数据传输的通道,它可以进行读、写或同时进行读写操作。常见的通道类型有 FileChannel(用于文件的读写操作)、SocketChannel(用于通过 TCP 协议进行网络通信)、ServerSocketChannel(用于监听客户端的连接请求)和 DatagramChannel(用于通过 UDP 协议进行网络通信)。下面分别介绍 FileChannel 和 SocketChannel 的使用。

FileChannel 文件操作

FileChannel 主要用于文件的读写操作,它可以实现对文件的高效读写,支持随机访问和内存映射等高级操作。以下是使用 FileChannel 读取和写入文件的示例:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class FileChannelExample {public static void main(String[] args) {try (FileInputStream fis = new FileInputStream("input.txt");FileOutputStream fos = new FileOutputStream("output.txt");FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel()) {// 创建缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 从输入通道读取数据到缓冲区while (inChannel.read(buffer) != -1) {// 切换缓冲区为读模式buffer.flip();// 从缓冲区写入数据到输出通道outChannel.write(buffer);// 清空缓冲区,为下一次读取做准备buffer.clear();}System.out.println("文件复制完成!");} catch (Exception e) {e.printStackTrace();}}
}

SocketChannel 网络操作

SocketChannel 用于通过 TCP 协议进行网络通信,可以实现非阻塞的网络通信,提高网络应用的并发性能。以下是一个简单的 SocketChannel 客户端和服务器端示例:

服务器端

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 NioServer {public static void main(String[] args) {try {// 创建选择器Selector selector = Selector.open();// 创建服务器套接字通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 将服务器通道注册到选择器上,监听连接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务器启动,监听端口8080...");while (true) {// 阻塞直到有事件发生selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 处理新连接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理读事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客户端消息: " + message);// 回显消息给客户端ByteBuffer outBuffer = ByteBuffer.wrap(("服务器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());}}}}} catch (IOException e) {e.printStackTrace();}}
}

客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;public class NioClient {public static void main(String[] args) {try {// 创建SocketChannelSocketChannel clientChannel = SocketChannel.open();clientChannel.configureBlocking(false);// 连接服务器clientChannel.connect(new InetSocketAddress("localhost", 8080));// 等待连接完成while (!clientChannel.finishConnect()) {// 可以做点别的事,或者稍微等等}System.out.println("已连接到服务器");// 发送消息String message = "你好,服务器!";ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());clientChannel.write(buffer);// 接收服务器回显消息buffer.clear();int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String response = new String(data);System.out.println("收到服务器回显: " + response);}// 关闭通道clientChannel.close();} catch (IOException e) {e.printStackTrace();}}
}

2.3 Selector 选择器

Selector 是 NIO 中的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。通过选择器,可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。以下是使用 Selector 的基本步骤和代码示例:

注册通道

首先需要创建一个 Selector,然后将通道注册到 Selector 上,并指定感兴趣的事件类型。例如,将 ServerSocketChannel 注册到 Selector 上,监听连接事件:

Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

监听事件
使用 selector 的 select 方法来监听注册通道上的事件。select 方法会阻塞,直到有感兴趣的事件发生。例如:

int readyChannels = selector.select();
if (readyChannels > 0) {// 处理就绪事件
}

处理就绪事件

通过 selectedKeys 方法获取已就绪的 SelectionKey 集合,然后遍历该集合,根据不同的事件类型处理相应的通道。例如:

Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 处理新连接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理读事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客户端消息: " + message);// 回显消息给客户端ByteBuffer outBuffer = ByteBuffer.wrap(("服务器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());}}
}

完整的服务器端代码示例(包含 Selector 的使用):

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 SelectorExample {public static void main(String[] args) {try {// 创建选择器Selector selector = Selector.open();// 创建服务器套接字通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));// 将服务器通道注册到选择器上,监听连接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务器启动,监听端口8080...");while (true) {// 阻塞直到有事件发生selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 处理新连接ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();SocketChannel clientChannel = serverChannel.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理读事件SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客户端消息: " + message);// 回显消息给客户端ByteBuffer outBuffer = ByteBuffer.wrap(("服务器已收到消息: " + message).getBytes());clientChannel.write(outBuffer);} else if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());}}}}} catch (IOException e) {e.printStackTrace();}}
}

2.4 NIO 文件操作案例

为了更直观地展示 NIO 在文件操作中的优势,我们通过一个大文件高效读写的案例来进行说明。假设我们有一个大小为 1GB 的大文件,需要将其读取并复制到另一个文件中,对比传统 IO 和 NIO 的实现方式和性能表现。

传统 IO 实现

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class TraditionalIoExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();try (FileInputStream fis = new FileInputStream("largeFile.txt");FileOutputStream fos = new FileOutputStream("copy_largeFile.txt")) {byte[] buffer = new byte[1024];int length;while ((length = fis.read(buffer)) != -1) {fos.write(buffer, 0, length);}} catch (IOException e) {e.printStackTrace();}long endTime = System.currentTimeMillis();System.out.println("传统IO复制文件耗时: " + (endTime - startTime) + " 毫秒");}
}

NIO 实现

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;public class NioFileExample {public static void main(String[] args) {long startTime = System.currentTimeMillis();try (FileChannel inChannel = FileChannel.open(Paths.get("largeFile.txt"), StandardOpenOption.READ);FileChannel outChannel = FileChannel.open(Paths.get("copy_largeFile.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024); // 1MB缓冲区while (inChannel.read(buffer) != -1) {buffer.flip();outChannel.write(buffer);buffer.clear();}} catch (IOException e) {e.printStackTrace();}long endTime = System.currentTimeMillis();System.out.println("NIO复制文件耗时: " + (endTime - startTime) + " 毫秒");}
}

通过实际测试可以发现,NIO 在处理大文件读写时,由于其采用了缓冲区和通道的机制,减少了系统调用次数和数据拷贝次数,相比传统 IO 具有更高的效率。在上述案例中,NIO 复制文件的耗时通常会明显低于传统 IO,尤其是在处理超大文件时,这种优势更加显著。这是因为 NIO 的缓冲区可以一次性读取和写入大量数据,减少了 I/O 操作的频率,从而提高了文件读写的性能。

三、NIO2.0 实战

Java NIO2.0 是 Java 7 引入的一组增强的 I/O API,它在 NIO 的基础上提供了更强大、更便捷的文件和目录操作功能。NIO2.0 引入了新的类和接口,如 Path、Files 和 WatchService 等,使得文件系统的操作更加灵活和高效。接下来,我们将深入探讨 NIO2.0 中 Path 类和 Files 类的使用,并通过实战案例展示其强大功能。

3.1 Path 类

在 Java NIO2.0 中,Path 类用于表示文件系统中的路径,它是一个平台无关的抽象路径。Path 接口提供了一系列方法来操作路径,包括获取路径的各个部分、解析路径、规范化路径等。

路径表示

可以使用 Paths 类的 get 方法来创建 Path 对象。例如,创建一个表示文件路径的 Path 对象:

import java.nio.file.Path;
import java.nio.file.Paths;public class PathExample {public static void main(String[] args) {// 创建Path对象Path path = Paths.get("C:/Users/Username/Documents/example.txt");// 获取文件名System.out.println("文件名: " + path.getFileName());// 获取父路径System.out.println("父路径: " + path.getParent());// 获取根路径System.out.println("根路径: " + path.getRoot());}
}

路径操作

Path 接口提供了丰富的方法来操作路径,如拼接路径、规范化路径等。

  • 拼接路径:使用 resolve 方法可以将两个路径拼接在一起。例如:
Path path1 = Paths.get("C:/Users/Username");
Path path2 = path1.resolve("Documents/example.txt");
System.out.println("拼接后的路径: " + path2);
  • 规范化路径:使用 normalize 方法可以消除路径中的冗余部分,如 “./” 和 “…/”。例如:
Path unnormalizedPath = Paths.get("C:/Users/Username/../Documents/./example.txt");
Path normalizedPath = unnormalizedPath.normalize();
System.out.println("规范化后的路径: " + normalizedPath);

3.2 Files 类

Files 类是 NIO2.0 中用于文件操作的核心类,它提供了大量的静态方法来执行文件的创建、删除、复制、移动、读取和写入等操作。

文件创建

使用 createFile 方法可以创建一个新文件。例如:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.io.IOException;public class FilesExample {public static void main(String[] args) {Path filePath = Paths.get("C:/Users/Username/newfile.txt");try {Files.createFile(filePath);System.out.println("文件创建成功");} catch (IOException e) {e.printStackTrace();}}
}

文件删除

使用 delete 方法可以删除文件或空目录。如果要删除的文件或目录不存在,会抛出 NoSuchFileException 异常;如果要删除的目录非空,会抛出 DirectoryNotEmptyException 异常。例如:

Path filePath = Paths.get("C:/Users/Username/newfile.txt");
try {Files.delete(filePath);System.out.println("文件删除成功");
} catch (IOException e) {e.printStackTrace();
}

文件复制

使用 copy 方法可以复制文件。如果目标文件已存在,会抛出 FileAlreadyExistsException 异常。可以通过 StandardCopyOption.REPLACE_EXISTING 选项来覆盖已存在的文件。例如:

Path sourcePath = Paths.get("C:/Users/Username/source.txt");
Path targetPath = Paths.get("C:/Users/Username/target.txt");
try {Files.copy(sourcePath, targetPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);System.out.println("文件复制成功");
} catch (IOException e) {e.printStackTrace();
}

文件读取

使用 readAllBytes 方法可以读取文件的所有字节内容。例如:

Path filePath = Paths.get("C:/Users/Username/data.txt");
try {byte[] content = Files.readAllBytes(filePath);String data = new String(content);System.out.println("文件内容: " + data);
} catch (IOException e) {e.printStackTrace();
}

3.3 Files 类高级操作

除了基本的文件操作,Files 类还提供了一些高级操作方法,如获取文件属性和监听文件变化。

文件属性获取

可以使用 Files 类的 getAttribute 方法获取文件的各种属性,如文件大小、修改时间、创建时间等。例如,获取文件的大小和修改时间:

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;public class FileAttributesExample {public static void main(String[] args) {Path filePath = Paths.get("C:/Users/Username/data.txt");try {BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);System.out.println("文件大小: " + attrs.size() + " 字节");System.out.println("修改时间: " + attrs.lastModifiedTime());System.out.println("创建时间: " + attrs.creationTime());} catch (IOException e) {e.printStackTrace();}}
}

文件监听

NIO2.0 引入了 WatchService 来监听文件系统的变化,如文件的创建、修改和删除等。以下是一个简单的示例,展示如何监听指定目录下的文件变化:

import java.nio.file.*;
import java.io.IOException;public class FileWatcherExample {public static void main(String[] args) {try {// 创建WatchServiceWatchService watchService = FileSystems.getDefault().newWatchService();// 注册要监听的目录Path directory = Paths.get("C:/Users/Username/Documents");directory.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);System.out.println("开始监听目录: " + directory);while (true) {// 等待事件发生WatchKey key = watchService.take();for (WatchEvent<?> event : key.pollEvents()) {WatchEvent.Kind<?> kind = event.kind();@SuppressWarnings("unchecked")WatchEvent<Path> ev = (WatchEvent<Path>) event;Path fileName = ev.context();System.out.println("事件类型: " + kind + ", 文件: " + fileName);}// 重置WatchKeyboolean valid = key.reset();if (!valid) {break;}}} catch (IOException | InterruptedException e) {e.printStackTrace();}}
}

3.4 NIO2.0 实战案例

为了更好地展示 NIO2.0 的强大功能,我们通过一个目录遍历和文件筛选的案例来进行实践。假设我们需要遍历指定目录及其子目录,筛选出所有的 Java 源文件,并打印出它们的路径。

import java.nio.file.*;
import java.io.IOException;public class DirectoryTraversalExample {public static void main(String[] args) {Path directory = Paths.get("C:/Users/Username/Projects");try {Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {if (file.getFileName().toString().endsWith(".java")) {System.out.println("找到Java源文件: " + file);}return FileVisitResult.CONTINUE;}});} catch (IOException e) {e.printStackTrace();}}
}

在上述代码中,我们使用 Files 类的 walkFileTree 方法来遍历指定目录及其子目录。walkFileTree 方法接受两个参数,一个是要遍历的起始目录,另一个是实现了 FileVisitor 接口的对象。我们通过继承 SimpleFileVisitor 类(它是 FileVisitor 接口的一个适配器类,提供了默认的实现),并重写 visitFile 方法来实现文件筛选逻辑。在 visitFile 方法中,我们检查文件的扩展名是否为 “.java”,如果是,则打印出文件的路径。通过这个案例,我们可以看到 NIO2.0 提供的文件和目录操作功能非常强大和灵活,能够轻松应对各种复杂的文件处理需求。

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

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

相关文章

OpenCV 实战:图像模板匹配与旋转处理实现教程

目录 一、功能概述&#xff1a;代码能做什么&#xff1f; 二、环境准备&#xff1a;先搭好运行基础 1. 安装 Python 2. 安装 OpenCV 库 3. 准备图像文件 三、代码逐段解析&#xff1a;从基础到核心 1. 导入 OpenCV 库 2. 读取图像文件 3. 模板图像旋转&#xff1a;处理…

一、cadence的安装及入门教学(反相器的设计与仿真)

一、Cadence的安装 1、安装VMware虚拟机 2、安装带有cadence软件的Linux系统 注&#xff1a;网盘链接 分享链接&#xff1a;https://disk.ningsuan.com.cn/#s/8XaVdtRQ 访问密码&#xff1a;11111 所有文件压缩包及文档密码&#xff1a; Cadence_ic 3、安装tsmc18工艺库…

用ai写了个UE5插件

文章目录实际需求1.头文件2.源文件3.用法小结实际需求 这个需求来源于之前的一个项目&#xff0c;当时用了一个第三方插件&#xff0c;里边有一些绘制线段的代码&#xff0c;c层用的是drawdebugline&#xff0c;当时看底层&#xff0c;觉得应该没问题&#xff0c;不应该在rele…

机器学习从入门到精通 - 强化学习初探:Q-Learning到Deep Q-Network实战

机器学习从入门到精通 - 强化学习初探&#xff1a;从 Q-Learning 到 Deep Q-Network 实战 一、开场白&#xff1a;推开强化学习这扇门 不知道你有没有过这种感觉 —— 盯着一个复杂的系统&#xff0c;既想让它达到某个目标&#xff0c;又苦于无法用传统规则去精确描述每一步该怎…

【OpenHarmony文件管理子系统】文件访问接口解析

OpenHarmony文件访问接口&#xff08;filemanagement_file_api&#xff09; 概述 OpenHarmony文件访问接口&#xff08;filemanagement_file_api&#xff09;是开源鸿蒙操作系统中的核心文件系统接口&#xff0c;为应用程序提供了完整的文件IO操作能力。该项目基于Node-API&…

云手机运行是否消耗自身流量?

云手机运行是否消耗自身流量&#xff0c;取决于具体的使用场景和设置&#xff1a;若用户在连接云手机时&#xff0c;使用的是家中Wi-Fi、办公室局域网等非移动数据网络&#xff0c;那么在云手机运行过程中&#xff0c;基本不会消耗用户自身的移动数据流量&#xff0c;在家中连接…

JavaSe之多线程

一、多线程基本了解 1、多线程基本知识 1.进程:进入到内存中执行的应用程序 2.线程:内存和CPU之间开通的通道->进程中的一个执行单元 3.线程作用:负责当前进程中程序的运行.一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序 4.简单理解…

产品月报|睿本云8月产品功能迭代

睿本云8月更新已陆续上线&#xff01; 睿本云8月产品月报&#xff0c;点击查收&#x1f447;小程序支付成功弹窗广告、企业会员增加卡券销售和卡券退货模块、工厂端可批量新增多门店订货单、门店端和工厂端新增“极速订货”、商品调拨业务支持自定义多种流程配置等功能迭代更新…

融云:当我们谈论 AI 重构业务时,我们到底在谈论什么

所有业务都值得用 AI 重新做一次。 这句话正在从一句鼓舞人心的口号&#xff0c;演变为一场无人可避的商业现实。AI 带来的结构性机会&#xff0c;意味着企业有机会从根本上重构成本、效率与体验的曲线。但这一切最终都要回到一个无比务实的问题上&#xff1a; AI 究竟如何在我…

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1异常

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length 1异常问题解决一、问题背景二、错误现象三、原因分析核心问题&#xff1a;字符集不匹配四、解决过程试错路径记录五、最终方案1.创建launch.json文件&#xff0c;修改VSCode…

【C语言】深入理解指针(5)

目录 sizeof和strlen 1.sizeof 2.strlen 3. sizeof 和 strlen 的对比 sizeof和strlen 1.sizeof sizeo正名&#xff1a;sizeof是操作符&#xff0c;不是函数&#xff0c;sizeof是操作符&#xff0c;括号内如果有计算不会进行计算sizeof 是操作符&#xff0c;用于计算变量所…

动态代理设计模式

JDK动态代理实现 动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能.动态代理又被称为JDK代理或接口代理. 静态代理与动态代理的区别: 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文 动态代理是在运行时动态生成的,即编译…

《Html泛型魔法学院:用霍格沃茨风格网页教授集合框架》

一、项目概述 这个创意教学网页&#xff0c;将Java泛型与集合框架知识融入霍格沃茨魔法世界主题。通过沉浸式UI设计和交互式代码练习&#xff0c;让抽象的技术概念变得生动有趣。主要技术栈包括&#xff1a; HTML5语义化结构Tailwind CSS框架Font Awesome图标库纯JavaScript交…

学习PaddlePaddle--环境配置-PyCharm + Conda​

第一阶段&#xff1a;安装与配置 Python 和 Conda​​ 虽然 PyCharm 可以管理环境&#xff0c;但我们先独立准备好 Conda 环境&#xff0c;这样更清晰可靠。 ​​1. 安装 Miniconda (Python 环境管理)​​ 1. ​​下载​​&#xff1a; • 访问 Miniconda 官网。 • 选择 ​​M…

【数据库】Sql Server数据库中isnull、iif、case when三种方式的使用和空值判断

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂》。 这是《Sql Server》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录前言ISNULL用法c…

【蓝桥杯选拔赛真题64】C++最大空白区 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解

C++最大空白区 第十四届蓝桥杯青少年创意编程大赛C++选拔赛真题 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】 1、C++专栏 电子学会C++一级历年真题解析 电子学会C++二级历年真题解析

试用Augment编写python脚本实现智能家居3D环境交互响应

环境配置 VS Code中直接安装Augment扩展&#xff0c;然后邮箱登录就能获得7天的试用。 从如下位置安装3D建模软件Blender&#xff1a; https://www.blendercn.org/downloadme#xiazai Blender 是一款免费开源的 3D 创作套件。它支持整个三维流程&#xff1a;建模、绑定、动画…

【架构师干货】系统架构设计

1. 软件架构概述 从需求分析到软件设计之间的过渡过程称为软件架构。只要软件架构设计好了&#xff0c;整个软件就不会出现坍塌性的错误&#xff0c;即不会崩溃。 架构设计就是需求分配&#xff0c;将满足需求的职责分配到组件上。 软件架构为软件系统提供了一个结构、行为和属…

Java设计模式之结构型—享元模式

Java中最常用的设计模式-CSDN博客 把“不可变且可复用”的细粒度对象缓存起来&#xff0c;用“共享”代替“新建”&#xff0c;从而节省内存。 经典场景 字符串常量池、Integer.valueOf(-128~127)、Android Message.obtain() 游戏粒子、编辑器字形、地图瓦片、线程池中的任务…

cursor+python轻松实现电脑监控

小伙伴们&#xff0c;今天我们利用cursor不写一行代码开发一个电脑的系统状态监控小应用&#xff01;下载安装cursor&#xff1a;网址&#xff1a;https://www.cursor.com/cn下载后双击安装输入提示词&#xff1a; 制作一个winswos应用&#xff0c;实现显示时间精确到秒&…