学习 NIO 入门知识,重点了解通道(Channel)、缓冲区(Buffer)以及非阻塞 IO 的基础概念和用法。
一、NIO 核心概念总览
Java NIO(Non-blocking IO,非阻塞 IO)是 JDK 1.4 引入的新 IO 模型,核心是 缓冲区(Buffer) 、通道(Channel) 、选择器(Selector),解决了传统 BIO(阻塞 IO)效率低、资源占用高的问题。
- 传统 BIO:以 "流" 为核心,数据单向传输(输入流 / 输出流),且读写阻塞;
- NIO:以 "缓冲区" 为核心,数据先写入缓冲区再读取,通道双向传输,支持非阻塞模式。
二、缓冲区(Buffer):数据的容器
- 核心定义
Buffer 是一块内存区域,用于临时存储数据,所有 NIO 操作都通过 Buffer 完成(而非直接操作流)。常见的 Buffer 类型:
| 类型 | 对应基本数据类型 |
|---|---|
| ByteBuffer | byte(最常用) |
| CharBuffer | char |
| IntBuffer | int |
| LongBuffer | long |
| FloatBuffer | float |
| DoubleBuffer | double |
- Buffer 核心属性(必须掌握)
- capacity:缓冲区总容量(创建后不可变);
- position:当前操作的位置(初始为 0,读写时自动移动);
- limit:可操作数据的上限(写模式下 = capacity,读模式下 = 写时的 position);
- mark :标记位,可通过
mark()标记位置,reset()恢复到该位置。
- Buffer 核心操作示例(ByteBuffer)
java
运行
java
import java.nio.ByteBuffer;
public class BufferDemo {
public static void main(String[] args) {
// 1. 创建缓冲区:容量为 10 个字节
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println("初始化状态:");
printBuffer(buffer); // capacity=10, position=0, limit=10
// 2. 写入数据(写模式)
String data = "NIO";
buffer.put(data.getBytes());
System.out.println("\n写入数据后(写模式):");
printBuffer(buffer); // capacity=10, position=3, limit=10
// 3. 切换为读模式(flip():limit=position,position=0)
buffer.flip();
System.out.println("\n切换读模式后:");
printBuffer(buffer); // capacity=10, position=0, limit=3
// 4. 读取数据
byte[] readData = new byte[buffer.limit()];
buffer.get(readData);
System.out.println("\n读取的数据:" + new String(readData)); // 输出 NIO
System.out.println("读取后状态:");
printBuffer(buffer); // capacity=10, position=3, limit=3
// 5. 清空缓冲区(clear():position=0, limit=capacity,数据未删除,只是重置标记)
buffer.clear();
System.out.println("\n清空后状态:");
printBuffer(buffer); // capacity=10, position=0, limit=10
}
// 辅助方法:打印 Buffer 状态
private static void printBuffer(ByteBuffer buffer) {
System.out.printf("capacity: %d, position: %d, limit: %d%n",
buffer.capacity(), buffer.position(), buffer.limit());
}
}
- 关键方法解释
allocate(int capacity):创建堆内存缓冲区 (常用);allocateDirect(int capacity):创建直接内存缓冲区(效率更高,无需拷贝,但创建 / 销毁成本高);put():向缓冲区写入数据(写模式);flip():切换为读模式(核心);get():从缓冲区读取数据(读模式);clear():重置标记(并非删除数据),准备再次写入;rewind():重置 position 为 0,可重复读取数据;compact():将未读取的剩余数据移到缓冲区开头,切换为写模式(适合分批处理数据)。
三、通道(Channel):数据的传输通道
- 核心定义
Channel 是连接缓冲区和数据源(文件、网络)的双向通道,数据必须通过 Channel 读取 / 写入,且 Channel 支持非阻塞模式。
- 常见 Channel 类型
| 类型 | 用途 |
|---|---|
| FileChannel | 文件读写(阻塞 / 非阻塞) |
| SocketChannel | TCP 客户端通道 |
| ServerSocketChannel | TCP 服务端通道 |
| DatagramChannel | UDP 数据报通道 |
- FileChannel 示例(文件读写)
java
运行
java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelDemo {
public static void main(String[] args) throws Exception {
// 1. 准备文件:从 input.txt 读取,写入 output.txt
String inputPath = "input.txt"; // 需提前创建,写入任意内容
String outputPath = "output.txt";
// 2. 创建输入/输出流,获取 FileChannel
try (FileInputStream fis = new FileInputStream(inputPath);
FileOutputStream fos = new FileOutputStream(outputPath);
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel()) {
// 3. 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 4. 读取数据到缓冲区
while (inChannel.read(buffer) != -1) { // read() 返回 -1 表示读取完毕
buffer.flip(); // 切换为读模式
outChannel.write(buffer); // 将缓冲区数据写入输出通道
buffer.clear(); // 清空缓冲区,准备下一次读取
}
}
System.out.println("文件复制完成!");
}
}
- 关键方法解释
read(Buffer buffer):从通道读取数据到缓冲区,返回读取的字节数,-1 表示读取完毕;write(Buffer buffer):将缓冲区数据写入通道;transferFrom(Channel src, long position, long count):直接从源通道复制数据(零拷贝,效率极高);transferTo(long position, long count, Channel target):直接向目标通道复制数据(零拷贝)。
四、非阻塞 IO 基础(核心优势)
- 非阻塞 IO 定义
传统 BIO 中,线程调用 read()/write() 时会阻塞,直到数据读写完成;NIO 的非阻塞模式下,线程调用读写方法后,无论数据是否就绪,都会立即返回,线程可处理其他任务,避免资源浪费。
- 非阻塞 IO 核心:选择器(Selector)
Selector 是 NIO 实现非阻塞的核心,一个线程可通过 Selector 监听多个 Channel 的事件(连接、读、写),实现 "单线程管理多通道",大幅减少线程开销。
- 非阻塞 SocketChannel 简单示例
java
运行
java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NonBlockingClient {
public static void main(String[] args) throws Exception {
// 1. 创建 SocketChannel 并设置为非阻塞模式
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
// 2. 连接服务器(非阻塞:立即返回,不管是否连接成功)
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// 3. 等待连接完成
while (!socketChannel.finishConnect()) {
// 连接未就绪时,线程可处理其他任务
System.out.println("连接中,处理其他任务...");
Thread.sleep(100);
}
// 4. 写入数据
String msg = "Hello NIO Non-blocking!";
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
socketChannel.write(buffer);
// 5. 读取响应(非阻塞:无数据时返回 0,不会阻塞)
buffer.clear();
int readBytes = socketChannel.read(buffer);
if (readBytes > 0) {
buffer.flip();
byte[] response = new byte[buffer.limit()];
buffer.get(response);
System.out.println("服务器响应:" + new String(response));
} else if (readBytes == 0) {
System.out.println("暂无服务器响应,继续处理其他任务...");
}
// 6. 关闭通道
socketChannel.close();
}
}
五、BIO vs NIO 核心区别
| 特性 | BIO | NIO |
|---|---|---|
| 核心单位 | 流(Stream) | 缓冲区(Buffer) |
| 传输方向 | 单向(输入 / 输出流) | 双向(Channel) |
| 阻塞模式 | 仅阻塞 | 支持阻塞 / 非阻塞 |
| 线程模型 | 一连接一线程 | 单线程管理多连接 |
| 核心组件 | 流、线程 | Buffer、Channel、Selector |
总结
- Buffer 是 NIO 的数据容器,核心是
capacity/position/limit三个属性,flip()是读写模式切换的关键; - Channel 是双向数据通道,所有 NIO 数据传输必须通过 Channel,
FileChannel支持零拷贝提升效率; - 非阻塞 IO 是 NIO 的核心优势,通过 Selector 实现单线程管理多通道,解决 BIO 线程开销大的问题。
掌握这三个核心概念,就理解了 NIO 的基础框架,后续可深入学习 Selector 事件监听、多路复用等进阶内容。