第一部分:NIO概述

理解NIO与传统I/O的区别以及核心概念

学习目标

了解NIO与IO的主要区别,理解Buffer和Channel的核心作用

NIO与传统IO对比

特性 传统IO NIO
工作模式 阻塞式I/O 非阻塞I/O
数据流方向 单向流(InputStream/OutputStream) 双向通道(Channel)
缓冲机制 使用byte数组 使用Buffer对象
并发处理 每个连接一个线程 单线程管理多个连接
适用场景 连接数少的应用 高并发、大连接数的应用
NIO优势: NIO更适合高并发、高性能的网络应用,如聊天服务器、文件服务器等

NIO核心组件

  • Buffer(缓冲区):数据存储容器,提供高效的数据访问方式
  • Channel(通道):双向通信通道,连接数据源和目的地
  • Selector(选择器):I/O多路复用,单线程管理多个Channel
  • Charset(字符集):提供Unicode字符串与字节之间的转换

NIO工作流程

  1. 应用程序从Channel读取数据到Buffer
  2. 应用程序处理Buffer中的数据
  3. 应用程序将处理后的数据从Buffer写入Channel
  4. Selector监控多个Channel的就绪状态

第二部分:Buffer详解

深入理解缓冲区的关键属性和操作方法

学习目标

掌握Buffer的核心概念、创建方式和状态转换

Buffer核心属性

Position: 0 Limit: 8 Capacity: 8
属性 描述 常用方法
Capacity(容量) 缓冲区最大容量,创建后不能改变 capacity()
Position(位置) 下一个要读取或写入的位置 position()
Limit(限制) 第一个不能读取或写入的位置 limit()

Buffer创建方式

// 创建ByteBuffer(最常用) ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 堆缓冲区 ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); // 直接缓冲区 // 创建其他类型的Buffer CharBuffer charBuffer = CharBuffer.allocate(512); IntBuffer intBuffer = IntBuffer.allocate(256); DoubleBuffer doubleBuffer = DoubleBuffer.allocate(128); // 从数组创建Buffer byte[] byteArray = new byte[1024]; ByteBuffer bufferFromArray = ByteBuffer.wrap(byteArray); // 创建视图缓冲区 IntBuffer intView = byteBuffer.asIntBuffer();
选择策略: 堆内缓冲(HeapBuffer)适用于中小数据量,直接缓冲(DirectBuffer)减少一次内存拷贝,适合大文件操作或网络I/O

Buffer状态转换

写模式

position=0, limit=capacity

put()

flip

转换到读模式

limit = position, position=0

flip()

读模式

position < limit

get()

rewind

重读

position=0, limit不变

rewind()

clear

清空

重置position=0, limit=capacity

clear()

第三部分:Channel详解

了解不同类型通道的特点和使用方法

学习目标

掌握FileChannel、SocketChannel等核心通道的使用

FileChannel

用于文件读写操作,支持文件锁定和内存映射文件

// 创建FileChannel RandomAccessFile file = new RandomAccessFile("data.txt", "rw"); FileChannel fileChannel = file.getChannel(); // 文件锁定 FileLock lock = fileChannel.lock(); // 使用内存映射文件 MappedByteBuffer buffer = fileChannel.map( FileChannel.MapMode.READ_WRITE, 0, fileChannel.size());

文件通道是阻塞式的,不适用于高并发场景

SocketChannel

TCP客户端通道,支持非阻塞模式

// 打开SocketChannel SocketChannel socketChannel = SocketChannel.open(); // 连接服务器(非阻塞) socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("example.com", 8080)); // 完成连接 while (!socketChannel.finishConnect()) { // 等待连接完成 }

需要与Selector配合实现高性能网络通信

ServerSocketChannel

TCP服务器通道,监听传入连接

// 创建服务器通道 ServerSocketChannel server = ServerSocketChannel.open(); server.bind(new InetSocketAddress(8080)); server.configureBlocking(false); // 非阻塞模式 // 注册到Selector server.register(selector, SelectionKey.OP_ACCEPT); // 处理连接 while (true) { SocketChannel client = server.accept(); if (client != null) { // 处理客户端连接 } }

通常与Selector配合使用处理多个连接

通道基础操作

// 打开通道 SocketChannel channel = SocketChannel.open(); // 关闭通道 channel.close(); // 检查通道是否打开 if (channel.isOpen()) { // 执行操作 } // 设置阻塞模式 channel.configureBlocking(false); // 注册到选择器 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); // 从通道读取数据 ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = channel.read(buffer); // 写入数据到通道 buffer.flip(); // 切换到读模式 int bytesWritten = channel.write(buffer); // 强制刷新到存储设备(FileChannel) fileChannel.force(true);

第四部分:Buffer与Channel交互

掌握Buffer和Channel配合实现高效I/O操作

学习目标

理解Buffer和Channel的交互过程,实现文件复制和网络传输

文件复制示例

public class FileCopy { public static void main(String[] args) { String source = "source.txt"; String dest = "dest.txt"; try (FileInputStream fis = new FileInputStream(source); FileOutputStream fos = new FileOutputStream(dest); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel()) { // 创建直接缓冲区提高性能 ByteBuffer buffer = ByteBuffer.allocateDirect(4096); while (inChannel.read(buffer) != -1) { // 准备读取缓冲区数据 buffer.flip(); // 写入到目标通道 outChannel.write(buffer); // 清空缓冲区为下一次写入准备 buffer.clear(); } System.out.println("文件复制完成"); } catch (IOException e) { e.printStackTrace(); } } }
性能技巧: 使用直接缓冲区(allocateDirect)可以减少内存拷贝,提高大文件复制性能

网络数据传输

public class SimpleClient { public static void main(String[] args) { try (SocketChannel socketChannel = SocketChannel.open()) { // 连接服务器 socketChannel.connect(new InetSocketAddress("localhost", 8080)); // 准备发送的消息 String message = "Hello NIO Server!"; ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); // 发送数据 while (buffer.hasRemaining()) { socketChannel.write(buffer); } // 准备接收响应 buffer.clear(); socketChannel.read(buffer); buffer.flip(); // 处理响应 byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); System.out.println("收到响应: " + new String(bytes)); } catch (IOException e) { e.printStackTrace(); } } }

第五部分:常见错误与优化

避免常见错误并优化NIO应用性能

学习目标

识别并解决Buffer和Channel使用中的常见问题

常见错误

  • 未调用flip()方法:导致读取无效数据
  • 缓冲区溢出:put操作超过buffer.capacity()
  • 未正确处理Buffer状态:读写模式混淆
  • 直接缓冲区内存释放:可能导致内存泄漏
  • 未关闭通道:导致资源泄漏
重要提醒: 使用直接缓冲区后,即使调用clear()也不会真正释放内存,需要等待垃圾回收器回收

性能优化建议

  • 使用直接缓冲区:适合大量数据传输
  • 合理设置缓冲区大小:避免频繁的I/O操作
  • 批量操作:使用Scatter/Gather减少系统调用
  • 内存映射文件:提高大文件访问性能
  • 使用Buffer池:避免频繁创建/销毁缓冲区

第六部分:实战练习

通过实战项目巩固NIO知识

学习目标

完成以下练习以巩固所学的Buffer和Channel知识

实战练习

练习1:Buffer操作模拟器

创建一个程序模拟Buffer操作:

  • 实现Buffer的allocate、put、get、flip、clear等方法
  • 显示当前Buffer的属性:position, limit, capacity
  • 验证缓冲区操作的边界条件

练习2:NIO文件工具

创建一个文件工具类,支持:

  • 高效文件复制
  • 文件分割与合并
  • 文件内容比较
  • 支持直接缓冲和内存映射

练习3:简单NIO服务器

实现一个支持以下功能的NIO服务器:

  1. 监听指定端口,接受客户端连接
  2. 支持同时处理多个客户端连接
  3. 接收客户端消息并返回响应
  4. 客户端断开连接后清理资源
  5. 支持简单的聊天功能