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

NIO知识点

一、Java NIO 基础概念

Java NIO(New Input/Output)是从 Java 1.4 版本开始引入的新的 IO API,它提供了与标准 IO 不同的工作方式。主要特点包括:

  1. 面向缓冲区:数据读取到一个稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加了处理过程中的灵活性。
  2. 非阻塞 IO:允许线程在等待数据时执行其他任务,提高了系统的吞吐量和响应性。
  3. 选择器:一个选择器可以监听多个通道的事件(如连接打开、数据到达等),从而让一个线程可以处理多个通道。

二、核心组件详解

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 的核心组件,它允许一个线程处理多个通道的事件。通过使用选择器,线程可以监听多个通道的连接、数据到达等事件,从而高效地管理多个通道。

使用步骤

  1. 创建 Selector:Selector selector = Selector.open();
  2. 将通道注册到选择器:channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
  3. 轮询选择器:selector.select();
  4. 获取就绪的事件:Set<SelectionKey> selectedKeys = selector.selectedKeys();
  5. 处理事件:遍历 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 性能优化建议

  1. 合理设置缓冲区大小:过小的缓冲区会增加 IO 操作次数,过大的缓冲区会浪费内存。

  2. 避免不必要的上下文切换:非阻塞 IO 的优势在于一个线程可以处理多个通道,避免创建过多线程导致上下文切换开销。

  3. 使用直接缓冲区:对于需要频繁进行 IO 操作的数据,使用直接缓冲区(ByteBuffer.allocateDirect())可以减少内存拷贝。

  4. 优化选择器配置:避免在选择器上注册过多通道,可根据系统资源和负载情况进行合理划分。

  5. 合理处理并发:在多线程环境下,注意对共享资源的同步访问,避免数据竞争。

相关文章:

  • 电路笔记(通信):CAN 仲裁机制(Arbitration Mechanism) 位级监视线与特性先占先得非破坏性仲裁
  • 回车键为什么叫做“回车键”?
  • Spring Boot 应用中实现配置文件敏感信息加密解密方案
  • LINUX530 rsync定时同步 环境配置
  • 量化qmt跟单聚宽小市值策略开发成功
  • [春秋云镜] CVE-2023-23752 writeup
  • 前端面试准备-3
  • Agent + MCP工具实现数据库查询
  • 深度剖析Node.js的原理及事件方式
  • day14 leetcode-hot100-25(链表4)
  • 动态规划之网格图模型(一)
  • 单元测试报错
  • 【ClickHouse】RollingBitmap
  • [3D GISMesh]三角网格模型中的孔洞修补算法
  • Ubuntu 18.04 上源码安装 protobuf 3.7.0
  • java/mysql/ES下的日期类型分析
  • 1、python代码实现与大模型的问答交互
  • 关于位图Bitmaps的介绍
  • js 动画库、2048核心逻辑、面试题add[1][2][3]+4
  • 湖北理元理律师事务所:债务优化中的生活保障实践
  • 网站怎样才有流量/网上营销怎么做
  • 软件开发商有哪些/汕头seo外包公司
  • 网站登录流程图/苏州网站外包
  • jsp和php哪个做网站快/百度竞价排名广告
  • 做国外网站独特密码/微商如何引流与推广
  • 做文学网站用什么域名/小说推广平台有哪些