Java NIO [非阻塞 + 多路复用解]

Java NIO 详解

Java NIO(New IO)是 JDK 1.4 引入的非阻塞 I/O 模型 ,核心目标是解决传统 BIO(Blocking IO)在高并发场景下的性能瓶颈。它基于「通道(Channel)+ 缓冲区(Buffer)+ 选择器(Selector)」的设计,支持非阻塞、面向缓冲区、多路复用,广泛用于高并发网络编程(如服务器、中间件)和高效文件操作。

一、Java NIO 核心特性

与传统 BIO 对比,NIO 的核心优势:

特性 Java BIO(传统 IO) Java NIO
数据操作方式 面向(Stream),单向 面向缓冲区(Buffer),双向
阻塞特性 同步阻塞(Blocking) 同步非阻塞(Non-Blocking)+ 多路复用
线程模型 一个连接一个线程(高开销) 单线程 / 少量线程管理多连接
适用场景 低并发、简单 IO 操作 高并发、高吞吐量场景(如 Netty 底层)

二、NIO 三大核心组件

1. 缓冲区(Buffer):数据的容器

所有 NIO 操作都通过缓冲区进行 ------ 数据从通道(Channel)读取到缓冲区,或从缓冲区写入通道。Buffer 是一个固定大小的数组,支持基本数据类型(ByteBuffer、CharBuffer、IntBuffer 等),核心是通过 4 个属性控制数据读写:

属性 作用 读写模式切换逻辑
capacity 缓冲区最大容量(创建后不可变) -
position 当前读写位置(初始为 0) 写时自增,读时通过 flip() 重置为 0
limit 读写的边界(写时 = capacity,读时 = 写的总长度) flip() 后设为当前 position
mark 标记一个位置,用于 reset() 回退 可选,需配合 mark()reset()
Buffer 核心方法
  • put():写入数据到缓冲区(position 自增);
  • get():从缓冲区读取数据(position 自增);
  • flip():切换为读模式(limit = position,position = 0);
  • rewind():重置读位置(position = 0,limit 不变);
  • clear():清空缓冲区(重置 position=0、limit=capacity,数据未真正删除);
  • compact():压缩缓冲区(保留未读取数据,将其移到开头,position 指向数据末尾)。
示例:ByteBuffer 基本使用
java 复制代码
import java.nio.ByteBuffer;

public class BufferDemo {
    public static void main(String[] args) {
        // 1. 创建缓冲区(容量 10 字节)
        ByteBuffer buffer = ByteBuffer.allocate(10);
        System.out.println("初始化:" + buffer); // position=0, limit=10, capacity=10

        // 2. 写入数据(写模式)
        buffer.put("hello".getBytes());
        System.out.println("写入后:" + buffer); // position=5, limit=10, capacity=10

        // 3. 切换为读模式
        buffer.flip();
        System.out.println("flip 后:" + buffer); // position=0, limit=5, capacity=10

        // 4. 读取数据
        byte[] dest = new byte[buffer.limit()];
        buffer.get(dest);
        System.out.println("读取内容:" + new String(dest)); // hello
        System.out.println("读取后:" + buffer); // position=5, limit=5, capacity=10

        // 5. 重置读位置(重新读取)
        buffer.rewind();
        System.out.println("rewind 后:" + buffer); // position=0, limit=5, capacity=10
    }
}

2. 通道(Channel):数据的传输通道

Channel 是 NIO 中双向的数据传输通道 (可读可写,区别于 BIO 流的单向),必须配合 Buffer 操作。通道是非阻塞的 (可通过 configureBlocking(false) 设置),支持异步读写。

常用 Channel 实现类
通道类型 作用 典型场景
FileChannel 文件 IO 操作(读 / 写文件) 大文件传输、文件复制
SocketChannel TCP 客户端通道(连接服务器、读写数据) 高并发客户端通信
ServerSocketChannel TCP 服务器通道(监听端口、接收连接) 高并发服务器(如 NIO 服务器)
DatagramChannel UDP 通道(无连接数据传输) 实时通信(如游戏、直播)
示例:FileChannel 复制文件(高效)
java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

public class FileChannelDemo {
    public static void main(String[] args) throws Exception {
        // 源文件和目标文件
        try (FileInputStream fis = new FileInputStream("source.txt");
             FileOutputStream fos = new FileOutputStream("target.txt");
             // 获取通道
             FileChannel inChannel = fis.getChannel();
             FileChannel outChannel = fos.getChannel()) {

            // 直接传输(零拷贝,效率极高)
            inChannel.transferTo(0, inChannel.size(), outChannel);
            // 或 outChannel.transferFrom(inChannel, 0, inChannel.size());
        }
        System.out.println("文件复制完成");
    }
}

3. 选择器(Selector):多路复用的核心

Selector 是 NIO 实现单线程管理多通道的关键,本质是「事件监听器」------ 一个线程通过 Selector 监听多个 Channel 的就绪事件(如连接就绪、读就绪、写就绪),仅当通道有事件时才处理,避免了 BIO 中线程阻塞等待的开销。

核心概念
  • 注册通道 :Channel 必须先注册到 Selector,且设置为非阻塞模式channel.configureBlocking(false));

  • 事件类型

    :Selector 监听的 4 种事件(用SelectionKey表示):

    • SelectionKey.OP_ACCEPT:接收连接就绪(ServerSocketChannel 专属);
    • SelectionKey.OP_CONNECT:连接就绪(SocketChannel 专属);
    • SelectionKey.OP_READ:读就绪(通道有数据可读);
    • SelectionKey.OP_WRITE:写就绪(通道可写入数据);
  • SelectionKey:通道注册到 Selector 后返回的「事件句柄」,包含通道、选择器、事件类型等信息。

Selector 工作流程
  1. 创建 Selector;
  2. 通道设置为非阻塞模式,注册到 Selector 并指定监听事件;
  3. 调用 selector.select() 阻塞等待就绪事件(返回就绪事件数);
  4. 遍历就绪的 SelectionKey 集合,处理对应事件(如接收连接、读数据);
  5. 移除已处理的 SelectionKey,重复步骤 3。
示例:NIO 单线程 TCP 服务器
java 复制代码
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 NioTcpServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建 Selector
        Selector selector = Selector.open();

        // 2. 创建 ServerSocketChannel 并绑定端口
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false); // 非阻塞模式

        // 3. 注册到 Selector,监听「接收连接」事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO 服务器启动,监听端口 8080...");

        while (true) {
            // 4. 阻塞等待就绪事件(返回就绪事件数)
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;

            // 5. 遍历就绪的 SelectionKey
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();

                // 处理「接收连接」事件(ServerSocketChannel)
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = server.accept(); // 接收客户端连接
                    clientChannel.configureBlocking(false); // 客户端通道非阻塞
                    // 注册客户端通道到 Selector,监听「读就绪」事件
                    clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                    System.out.println("客户端连接:" + clientChannel.getRemoteAddress());
                }

                // 处理「读就绪」事件(SocketChannel)
                if (key.isReadable()) {
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment(); // 获取绑定的缓冲区

                    // 读取客户端数据
                    int readLen = clientChannel.read(buffer);
                    if (readLen == -1) { // 客户端断开连接
                        clientChannel.close();
                        key.cancel();
                        System.out.println("客户端断开连接:" + clientChannel.getRemoteAddress());
                        continue;
                    }

                    // 切换读模式,打印数据
                    buffer.flip();
                    byte[] data = new byte[buffer.limit()];
                    buffer.get(data);
                    System.out.println("收到数据:" + new String(data) + "(来自:" + clientChannel.getRemoteAddress() + ")");

                    // 重置缓冲区,准备下次读取
                    buffer.clear();
                }

                // 移除已处理的 Key(避免重复处理)
                iterator.remove();
            }
        }
    }
}

三、NIO 其他重要组件

1. 管道(Pipe):线程间通信

Pipe 是 NIO 提供的单向管道,用于两个线程间的通信,包含两个通道:

  • Pipe.SourceChannel:读通道(接收数据);
  • Pipe.SinkChannel:写通道(发送数据)。
示例:Pipe 线程通信
java 复制代码
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

public class PipeDemo {
    public static void main(String[] args) throws Exception {
        // 创建管道
        Pipe pipe = Pipe.open();

        // 写线程:向 SinkChannel 写入数据
        new Thread(() -> {
            try {
                Pipe.SinkChannel sinkChannel = pipe.sink();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put("Hello from Pipe!".getBytes());
                buffer.flip();
                sinkChannel.write(buffer);
                sinkChannel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

        // 读线程:从 SourceChannel 读取数据
        new Thread(() -> {
            try {
                Pipe.SourceChannel sourceChannel = pipe.source();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int readLen = sourceChannel.read(buffer);
                buffer.flip();
                System.out.println("读取到数据:" + new String(buffer.array(), 0, readLen));
                sourceChannel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
}

2. 文件锁(FileLock):并发文件安全

FileChannel 支持文件锁,用于多线程 / 多进程并发操作文件时的同步:

java 复制代码
// 获取文件锁(独占锁)
FileLock lock = fileChannel.lock();
// 或获取共享锁(仅读)
// FileLock lock = fileChannel.lock(0, FileChannel.MapMode.READ_ONLY, true);

// 操作文件...

lock.release(); // 释放锁

3. 字符集(Charsets):编码解码

NIO 提供 Charsets 类处理字符编码(如 UTF-8、GBK),配合 CharBufferByteBuffer 实现字符串与字节的转换:

java 复制代码
import java.nio.charset.Charset;
import java.nio.CharBuffer;
import java.nio.ByteBuffer;

public class CharsetDemo {
    public static void main(String[] args) {
        Charset utf8 = Charset.forName("UTF-8");

        // 字符串 → 字节(编码)
        String str = "你好,NIO";
        ByteBuffer byteBuffer = utf8.encode(str);

        // 字节 → 字符串(解码)
        CharBuffer charBuffer = utf8.decode(byteBuffer);
        System.out.println(charBuffer.toString()); // 你好,NIO
    }
}

四、NIO 适用场景与注意事项

适用场景

  1. 高并发网络编程:如服务器、网关(如 Netty、Tomcat 8+ 底层);
  2. 高效文件操作:大文件复制、分片传输(FileChannel 零拷贝);
  3. 线程间通信:Pipe 替代传统的 Thread.join () 或队列,更高效。

注意事项

  1. 通道必须设置为非阻塞模式才能注册到 Selector;
  2. selector.select() 是阻塞方法,可通过 select(long timeout) 设置超时,或 wakeup() 唤醒;
  3. Buffer 的 flip()clear() 等方法容易用错,需注意读写模式切换;
  4. 非阻塞 IO 需处理「部分读写」(如 channel.read(buffer) 可能只读取部分数据)。

五、总结

Java NIO 以「通道 + 缓冲区 + 选择器」为核心,通过非阻塞 + 多路复用解决了 BIO 高并发瓶颈,是高性能 Java 编程的基础。实际开发中,直接使用 NIO API 较为繁琐,通常会基于封装后的框架(如 Netty、Mina)进行开发,但理解 NIO 核心原理是掌握这些框架的关键。

相关推荐
猪八戒1.01 小时前
onenet接口
开发语言·前端·javascript·嵌入式硬件
程序猿小蒜1 小时前
基于Spring Boot的宠物领养系统的设计与实现
java·前端·spring boot·后端·spring·宠物
合作小小程序员小小店1 小时前
web网页开发,在线%食堂管理%系统,基于Idea,html,css,jQuery,java,ssm,mysql。
java·前端·mysql·html·intellij-idea·jquery
h***83931 小时前
JavaScript开源
开发语言·javascript·ecmascript
奋斗的小高1 小时前
Docker 安装与使用
java
Z***25801 小时前
JavaScript虚拟现实案例
开发语言·javascript·vr
毕设源码-钟学长1 小时前
【开题答辩全过程】以 浮生馆汉服租赁管理系统为例,包含答辩的问题和答案
android·java·tomcat
90后小陈老师1 小时前
用户管理系统 07 项目前端初始化 | 新手实战 | 期末实训 | Java+SpringBoot+Vue
java·前端·spring boot
Coder-coco2 小时前
点餐|智能点餐系统|基于java+ Springboot的动端的点餐系统小程序(源码+数据库+文档)
java·vue.js·spring boot·小程序·论文·毕设·电子点餐系统