NIO知识点
一、Java NIO 基础概念
Java NIO(New Input/Output)是从 Java 1.4 版本开始引入的新的 IO API,它提供了与标准 IO 不同的工作方式。主要特点包括:
- 面向缓冲区:数据读取到一个稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加了处理过程中的灵活性。
- 非阻塞 IO:允许线程在等待数据时执行其他任务,提高了系统的吞吐量和响应性。
- 选择器:一个选择器可以监听多个通道的事件(如连接打开、数据到达等),从而让一个线程可以处理多个通道。
二、核心组件详解
1. 缓冲区(Buffer)
缓冲区是一个用于存储特定基本数据类型的容器,所有缓冲区都具有以下属性:
- capacity:容量,即缓冲区能够容纳的数据元素的最大数量,在创建时确定且不能更改。
- position:位置,下一个要读取或写入的数据元素的索引。
- limit:限制,缓冲区中可以读取或写入的最大位置,位于 limit 后的数据不可读写。
- mark:标记,一个备忘位置,调用 mark () 方法将 mark 设为当前的 position 值,之后可通过 reset () 方法将 position 恢复到标记的位置。
常用方法:
allocate(int capacity)
:创建一个指定容量的缓冲区put(data)
:向缓冲区写入数据flip()
:切换到读模式(将 limit 设为 position,position 设为 0)get()
:从缓冲区读取数据clear()
:清空缓冲区(将 position 设为 0,limit 设为 capacity)rewind()
:重置缓冲区(将 position 设为 0,mark 被丢弃)
示例代码:
import java.nio.IntBuffer;public class BufferExample {public static void main(String[] args) {// 创建一个容量为10的IntBufferIntBuffer buffer = IntBuffer.allocate(10);// 向缓冲区写入数据for (int i = 0; i < 5; i++) {buffer.put(i);}// 切换到读模式buffer.flip();// 从缓冲区读取数据while (buffer.hasRemaining()) {System.out.print(buffer.get() + " ");}System.out.println();// 重置缓冲区,以便再次读取buffer.rewind();// 再次读取数据System.out.println("Rewind后再次读取:");while (buffer.hasRemaining()) {System.out.print(buffer.get() + " ");}// 清空缓冲区buffer.clear();System.out.println("\nclear后position: " + buffer.position());System.out.println("clear后limit: " + buffer.limit());}
}
2. 通道(Channel)
通道是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。与流不同的是,通道是双向的,支持异步读写操作,且通道始终与缓冲区交互。
常用通道实现:
FileChannel
:用于文件读写SocketChannel
:用于 TCP 网络通信的客户端ServerSocketChannel
:用于 TCP 网络通信的服务器端DatagramChannel
:用于 UDP 网络通信
示例代码:使用 FileChannel 复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class FileChannelExample {public static void main(String[] args) {try (FileInputStream fis = new FileInputStream("source.txt");FileOutputStream fos = new FileOutputStream("destination.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 (IOException e) {e.printStackTrace();}}
}
3. 选择器(Selector)
选择器是 Java NIO 中实现非阻塞 IO 的核心组件,它允许一个线程处理多个通道的事件。通过使用选择器,线程可以监听多个通道的连接、数据到达等事件,从而高效地管理多个通道。
使用步骤:
- 创建 Selector:
Selector selector = Selector.open();
- 将通道注册到选择器:
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
- 轮询选择器:
selector.select();
- 获取就绪的事件:
Set<SelectionKey> selectedKeys = selector.selectedKeys();
- 处理事件:遍历 selectedKeys,根据 key 的类型处理相应的事件
示例代码:使用 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 NioServerExample {private static final int PORT = 8080;public static void main(String[] args) {try (Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {// 配置服务器套接字通道serverSocketChannel.socket().bind(new InetSocketAddress(PORT));serverSocketChannel.configureBlocking(false);// 将服务器套接字通道注册到选择器,监听接受连接事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服务器启动,监听端口: " + PORT);// 轮询选择器while (true) {// 阻塞等待就绪的通道selector.select();// 获取就绪的事件集合Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();// 处理每个就绪的事件while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 处理接受连接事件if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();SocketChannel socketChannel = ssc.accept();System.out.println("接受新连接: " + socketChannel);// 配置客户端通道为非阻塞模式,并注册到选择器,监听读取事件socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);}// 处理读取事件else if (key.isReadable()) {SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);// 读取客户端数据int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data, "UTF-8");System.out.println("收到客户端消息: " + message);// 回写响应给客户端String response = "服务器已收到消息: " + message;ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());socketChannel.write(responseBuffer);} else if (bytesRead == -1) {// 客户端关闭连接System.out.println("客户端关闭连接: " + socketChannel);socketChannel.close();}}// 移除已处理的事件keyIterator.remove();}}} catch (IOException e) {e.printStackTrace();}}
}
三、非阻塞 IO 编程实践
1. 非阻塞客户端实现
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;public class NioClientExample {private static final String SERVER_HOST = "localhost";private static final int SERVER_PORT = 8080;public static void main(String[] args) {try (SocketChannel socketChannel = SocketChannel.open();Scanner scanner = new Scanner(System.in)) {// 连接服务器socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));System.out.println("已连接到服务器: " + socketChannel);// 配置为非阻塞模式socketChannel.configureBlocking(false);// 创建缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 读取用户输入并发送给服务器while (true) {System.out.print("请输入要发送的消息(输入exit退出): ");String message = scanner.nextLine();if ("exit".equalsIgnoreCase(message)) {break;}// 发送消息给服务器buffer.clear();buffer.put(message.getBytes("UTF-8"));buffer.flip();socketChannel.write(buffer);// 接收服务器响应buffer.clear();int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String response = new String(data, "UTF-8");System.out.println("收到服务器响应: " + response);}}} catch (IOException e) {e.printStackTrace();}}
}
2. 基于选择器的多客户端处理
上面的 NioServerExample 已经展示了基于选择器的服务器实现,它可以同时处理多个客户端连接。关键点在于:
- 服务器通道注册 OP_ACCEPT 事件以接受新连接
- 客户端通道注册 OP_READ 事件以读取客户端数据
- 单个线程通过 Selector 轮询所有注册的通道,处理就绪的事件
四、NIO 高级应用
1. 文件锁定
Java NIO 提供了文件锁定机制,可以锁定文件的一部分或全部,防止其他程序访问。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;public class FileLockingExample {public static void main(String[] args) {try (RandomAccessFile file = new RandomAccessFile("test.txt", "rw");FileChannel channel = file.getChannel()) {// 获取文件锁(锁定整个文件)FileLock lock = channel.lock();System.out.println("文件已锁定");// 执行文件操作// ...// 释放锁lock.release();System.out.println("文件锁已释放");} catch (IOException e) {e.printStackTrace();}}
}
2. 内存映射文件
内存映射文件允许将文件的一部分或全部映射到内存中,这样可以像访问内存一样访问文件,提高 IO 效率。
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;public class MemoryMappedFileExample {private static final int SIZE = 1024 * 1024; // 1MBpublic static void main(String[] args) {try (RandomAccessFile file = new RandomAccessFile("mapped_file.dat", "rw");FileChannel channel = file.getChannel()) {// 创建内存映射文件MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);// 写入数据for (int i = 0; i < SIZE; i++) {buffer.put((byte) 'A');}// 读取数据buffer.position(0);for (int i = 0; i < 10; i++) {System.out.print((char) buffer.get());}System.out.println();System.out.println("内存映射文件操作完成");} catch (IOException e) {e.printStackTrace();}}
}
3. 异步 IO(AIO)
Java 7 引入了 NIO.2,提供了真正的异步 IO 支持,通过 CompletionHandler 回调或 Future 对象获取操作结果。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.Future;public class AioServerExample {private static final int PORT = 8090;public static void main(String[] args) throws IOException, InterruptedException {// 创建异步服务器套接字通道AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));System.out.println("异步服务器启动,监听端口: " + PORT);// 接受客户端连接serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {@Overridepublic void completed(AsynchronousSocketChannel clientChannel, Void attachment) {// 继续接受下一个连接serverChannel.accept(null, this);// 处理当前连接handleClient(clientChannel);}@Overridepublic void failed(Throwable exc, Void attachment) {exc.printStackTrace();}});// 保持主线程运行Thread.sleep(Long.MAX_VALUE);}private static void handleClient(AsynchronousSocketChannel clientChannel) {try {System.out.println("接受新连接: " + clientChannel.getRemoteAddress());// 创建缓冲区ByteBuffer buffer = ByteBuffer.allocate(1024);// 异步读取数据clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer bytesRead, ByteBuffer buffer) {if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("收到客户端消息: " + message);// 回写响应String response = "服务器已收到: " + message;ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());// 异步写入响应Future<Integer> writeResult = clientChannel.write(responseBuffer);try {writeResult.get(); // 等待写入完成System.out.println("响应已发送给客户端");} catch (Exception e) {e.printStackTrace();}} else if (bytesRead == -1) {// 客户端关闭连接try {System.out.println("客户端关闭连接: " + clientChannel.getRemoteAddress());clientChannel.close();} catch (IOException e) {e.printStackTrace();}}}@Overridepublic void failed(Throwable exc, ByteBuffer buffer) {try {System.out.println("读取失败,关闭连接: " + clientChannel.getRemoteAddress());clientChannel.close();} catch (IOException e) {e.printStackTrace();}}});} catch (IOException e) {e.printStackTrace();}}
}
五、NIO 性能优化建议
-
合理设置缓冲区大小:过小的缓冲区会增加 IO 操作次数,过大的缓冲区会浪费内存。
-
避免不必要的上下文切换:非阻塞 IO 的优势在于一个线程可以处理多个通道,避免创建过多线程导致上下文切换开销。
-
使用直接缓冲区:对于需要频繁进行 IO 操作的数据,使用直接缓冲区(
ByteBuffer.allocateDirect()
)可以减少内存拷贝。 -
优化选择器配置:避免在选择器上注册过多通道,可根据系统资源和负载情况进行合理划分。
-
合理处理并发:在多线程环境下,注意对共享资源的同步访问,避免数据竞争。