Java IO与NIO的核心区别

Java IO与NIO的核心区别

Java 提供了三套 IO API:BIO(Blocking IO)、NIO(New IO)和 NIO.2(JDK 7+)。理解它们的差异对于高并发网络编程至关重要。

BIO:传统阻塞IO

// 服务端
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞等待连接
new Thread(() -> {
try {
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) != -1) { // 阻塞等待数据
// 处理数据
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}

特点

  • 一个连接一个线程
  • 简单易用,适合连接数少的场景
  • 线程上下文切换开销大

NIO:非阻塞IO

NIO 三大核心组件:

1. Buffer(缓冲区)

ByteBuffer buffer = ByteBuffer.allocate(1024);

// 写入数据到Buffer
buffer.put("hello".getBytes());

// 切换为读模式
buffer.flip();

// 从Buffer读取数据
while (buffer.hasRemaining()) {
byte b = buffer.get();
}

// 清空Buffer,切换为写模式
buffer.clear();

Buffer 状态变量:

  • position:当前读/写的位置
  • limit:读/写的上限
  • capacity:Buffer 的容量

2. Channel(通道)

Channel 是双向的,不同于 Stream 的单向:

// 文件通道
FileChannel channel = new FileInputStream("file.txt").getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer); // 从Channel读到Buffer

// Socket通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
socketChannel.configureBlocking(false); // 设置为非阻塞

3. Selector(选择器)

Selector 允许单线程管理多个 Channel:

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

while (true) {
selector.select(); // 阻塞等待就绪事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();

while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buf = ByteBuffer.allocate(1024);
client.read(buf);
// 处理数据
}
it.remove();
}
}

NIO.2:异步IO(JDK 7+)

Path path = Paths.get("file.txt");

// 异步文件通道
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 异步读取,通过回调处理结果
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("读取了 " + result + " 字节");
}

@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});

三者对比

特性 BIO NIO NIO.2 (AIO)
IO模型 阻塞 非阻塞 异步
线程数 1连接1线程 1线程管理多连接 回调驱动
编程复杂度 简单 较复杂 中等
适用场景 连接少、并发低 连接多、并发高 超大量连接
典型应用 Tomcat传统模式 Netty 少见

直接内存 vs 堆内存

// 堆内存Buffer(受GC管理)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

// 直接内存Buffer(不受GC管理,零拷贝)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

直接内存特点:

  • 读写速度快(避免 JVM 堆与 Native 堆之间的数据复制)
  • 分配和回收成本高
  • 适合长时间存活的大 Buffer

零拷贝(Zero Copy)

传统文件传输:

磁盘 → 内核缓冲区 → 用户缓冲区 → Socket缓冲区 → 网卡
(4次拷贝,4次上下文切换)

NIO 零拷贝(FileChannel.transferTo):

FileChannel source = new FileInputStream("source.txt").getChannel();
SocketChannel dest = SocketChannel.open();

source.transferTo(0, source.size(), dest);
// 磁盘 → 内核缓冲区 → 网卡
//(2次拷贝,2次上下文切换)

实际应用

Netty 的线程模型

EventLoopGroup bossGroup = new NioEventLoopGroup(1);   // 接收连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 处理IO

ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new HttpServerCodec());
ch.pipeline().addLast(new HttpObjectAggregator(65536));
ch.pipeline().addLast(new MyHandler());
}
});

Netty 基于 NIO 实现,采用 Reactor 模式:

  • Boss EventLoop:处理 Accept 事件
  • Worker EventLoop:处理 Read/Write 事件

总结

场景 推荐方案
简单文件读写 BIO(Files.readAllLines)
高并发网络服务 NIO(Netty)
需要零拷贝的文件传输 FileChannel.transferTo
超大量并发连接 NIO + 多 Reactor

理解 IO 模型是编写高性能 Java 应用的基础。对于现代高并发服务,基于 NIO 的 Netty 几乎是标准选择。


   转载规则


《Java IO与NIO的核心区别》 小乐 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录