24_Java NIO核心组件

Java NIO核心组件

文章目录

  • [Java NIO核心组件](#Java NIO核心组件)
    • 前言
    • 一、NIO与BIO的对比
    • [二、Buffer 缓冲区](#二、Buffer 缓冲区)
      • [2.1 Buffer的核心属性](#2.1 Buffer的核心属性)
      • [2.2 Buffer的基本操作](#2.2 Buffer的基本操作)
      • [2.3 Buffer的allocat和allocateDirect](#2.3 Buffer的allocat和allocateDirect)
      • [2.4 其他类型的Buffer](#2.4 其他类型的Buffer)
    • [三、Channel 通道](#三、Channel 通道)
      • [3.1 FileChannel 文件通道](#3.1 FileChannel 文件通道)
      • [3.2 使用FileChannel实现文件拷贝](#3.2 使用FileChannel实现文件拷贝)
      • [3.3 RandomAccessFile的Channel](#3.3 RandomAccessFile的Channel)
    • [四、Selector 选择器](#四、Selector 选择器)
      • [4.1 网络编程中的Selector](#4.1 网络编程中的Selector)
      • [4.2 NIO客户端示例](#4.2 NIO客户端示例)
    • 五、NIO三大组件关系总结
    • 总结
    • [✅ 亮点总结](#✅ 亮点总结)
    • 适用场景
    • 扩展方向

前言

Java NIO(New IO / Non-blocking IO)是从Java 1.4开始引入的一套全新的IO API,用于替代标准Java IO API。与传统BIO(Blocking IO)不同,NIO支持非阻塞IO操作,能够使用单个线程管理多个网络连接,极大提升了高并发场景下的性能。

如果你曾经用传统的BIO(一个连接一个线程)搭建过网络服务器,你一定遇到过"千连接问题"------1000个并发连接就意味着1000个线程,线程的上下文切换开销和内存占用很快就会压垮服务器。NIO正是为了解决这个根本性问题而诞生的:它用一个Selector线程监控数千个Channel ,只在有数据可读时才处理,实现了真正的IO多路复用。NIO的核心组件包括Channel(通道)Buffer(缓冲区)Selector(选择器)------这三个组件的关系就像"水管(Channel)、水桶(Buffer)和水泵(Selector)"。本文将从这三个核心组件出发,带你全面理解Java NIO的架构与使用方式。

一、NIO与BIO的对比

特性 BIO(传统IO) NIO(New IO)
数据流向 单向(Input/Output Stream) 双向(Channel)
阻塞模式 阻塞(读写时线程阻塞) 支持非阻塞
数据处理 面向流,逐字节处理 面向缓冲区,批量处理
多连接管理 一线程一连接 一选择器多通道
适用场景 连接数少、数据量大的场景 高并发、短连接的场景

二、Buffer 缓冲区

Buffer是一个存储数据的容器,本质是一个数组(byte\[\]、char\[\]、int\[\]等),但提供了结构化的数据访问方式。Buffer的设计是NIO体系中最具创新性的部分之一:它用一个数组模拟了一个支持"读写模式切换"的数据容器。

理解Buffer的难点在于它的状态管理------四个核心属性(capacity、limit、position、mark)的联动关系,以及flip()clear()等状态切换操作的作用。很多NIO初学者的第一个bug就出现在这里:写数据之前忘了调用clear(),导致position卡在旧位置;或者读数据之前忘了调用flip(),导致读不到有效数据。

2.1 Buffer的核心属性

java 复制代码
public abstract class Buffer {
    // 核心四个属性
    private int capacity;    // 缓冲区总容量(固定值)
    private int limit;       // 当前可读写数据的界限
    private int position;    // 下一个要读写的位置索引
    private int mark;        // 标记位置,用于reset()
}

capacity > limit >= position >= mark >= 0,这是Buffer的不变式。

2.2 Buffer的基本操作

java 复制代码
import java.nio.ByteBuffer;

public class BufferDemo {
    public static void main(String[] args) {
        // 分配10字节的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        printState(buffer, "初始状态");

        // 写入数据
        buffer.put((byte) 1);
        buffer.put((byte) 2);
        buffer.put((byte) 3);
        buffer.put((byte) 4);
        buffer.put((byte) 5);
        printState(buffer, "写入5个字节后");

        // flip():从写模式切换到读模式
        // limit = position, position = 0
        buffer.flip();
        printState(buffer, "flip()切换读模式后");

        // 读取数据
        System.out.print("读取数据:");
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get() + " ");
        }
        System.out.println();
        printState(buffer, "读取完毕后");

        // rewind():重新读取(position=0,limit不变)
        buffer.rewind();
        printState(buffer, "rewind()后");

        // clear():清空缓冲区(position=0, limit=capacity),准备写入
        buffer.clear();
        printState(buffer, "clear()后");
    }

    static void printState(ByteBuffer buffer, String stage) {
        System.out.printf("%-20s [position=%d, limit=%d, capacity=%d]%n",
                stage, buffer.position(), buffer.limit(), buffer.capacity());
    }
}

输出结果

复制代码
初始状态               [position=0, limit=10, capacity=10]
写入5个字节后          [position=5, limit=10, capacity=10]
flip()切换读模式后     [position=0, limit=5, capacity=10]
读取数据:1 2 3 4 5
读取完毕后             [position=5, limit=5, capacity=10]
rewind()后             [position=0, limit=5, capacity=10]
clear()后              [position=0, limit=10, capacity=10]

2.3 Buffer的allocat和allocateDirect

java 复制代码
// 堆缓冲区(JVM堆内存,受GC管理)
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);

// 直接缓冲区(堆外内存,不受GC管理,IO性能更好)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

// 包装已有数组
byte[] array = new byte[1024];
ByteBuffer wrapBuffer = ByteBuffer.wrap(array);

堆缓冲区 vs 直接缓冲区

  • 堆缓冲区(HeapByteBuffer):数据存储在JVM堆中,读写会有一次拷贝过程
  • 直接缓冲区(DirectByteBuffer):数据存储在堆外内存,操作系统可直接访问,IO性能更高但创建和销毁成本较大

2.4 其他类型的Buffer

java 复制代码
import java.nio.*;

public class BufferTypesDemo {
    public static void main(String[] args) {
        // 除了ByteBuffer,NIO还提供了其他基本类型的Buffer
        CharBuffer charBuf = CharBuffer.allocate(100);
        charBuf.put("Hello NIO");
        charBuf.flip();
        while (charBuf.hasRemaining()) {
            System.out.print(charBuf.get());
        }
        System.out.println();

        IntBuffer intBuf = IntBuffer.allocate(10);
        for (int i = 0; i < 10; i++) {
            intBuf.put(i * i);
        }
        intBuf.flip();
        System.out.print("平方数:");
        while (intBuf.hasRemaining()) {
            System.out.print(intBuf.get() + " ");
        }

        // DoubleBuffer, FloatBuffer, LongBuffer, ShortBuffer 类似
    }
}

三、Channel 通道

Channel代表与数据源(文件、Socket等)的连接,是双向的,既可以读也可以写。

3.1 FileChannel 文件通道

java 复制代码
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelDemo {
    public static void main(String[] args) throws IOException {
        // 写入文件
        try (FileOutputStream fos = new FileOutputStream("D:/test/nio_write.txt");
             FileChannel channel = fos.getChannel()) {

            String data = "Java NIO FileChannel 写入示例\r\n第二行数据\r\n";
            ByteBuffer buffer = ByteBuffer.wrap(data.getBytes());
            channel.write(buffer);
            System.out.println("写入完成,写入字节数:" + data.getBytes().length);
        }

        // 读取文件
        try (FileInputStream fis = new FileInputStream("D:/test/nio_write.txt");
             FileChannel channel = fis.getChannel()) {

            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = channel.read(buffer);
            while (bytesRead != -1) {
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                buffer.clear();
                bytesRead = channel.read(buffer);
            }
        }
    }
}

3.2 使用FileChannel实现文件拷贝

java 复制代码
import java.io.*;
import java.nio.channels.FileChannel;

public class FileChannelCopy {
    public static void main(String[] args) {
        String src = "D:/test/source_large.dat";
        String dest = "D:/test/dest_nio.dat";

        try (FileChannel srcChannel = new FileInputStream(src).getChannel();
             FileChannel destChannel = new FileOutputStream(dest).getChannel()) {

            // 方式一:transferTo
            long size = srcChannel.size();
            long transferred = srcChannel.transferTo(0, size, destChannel);
            System.out.println("transferTo拷贝了 " + transferred + " 字节");

            // 方式二:transferFrom
            // destChannel.transferFrom(srcChannel, 0, srcChannel.size());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

transferTo()和transferFrom() 利用了操作系统的**零拷贝(zero-copy)**技术,数据传输不需要经过用户空间,效率极高。

3.3 RandomAccessFile的Channel

java 复制代码
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class RandomAccessFileDemo {
    public static void main(String[] args) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile("D:/test/random.txt", "rw");
             FileChannel channel = raf.getChannel()) {

            // 写入数据
            ByteBuffer writeBuf = ByteBuffer.wrap("ABCDEFGHIJ".getBytes());
            channel.write(writeBuf);

            // 定位到文件开头,读取前5个字节
            channel.position(0);
            ByteBuffer readBuf = ByteBuffer.allocate(5);
            channel.read(readBuf);
            readBuf.flip();
            System.out.print("前5个字节:");
            while (readBuf.hasRemaining()) {
                System.out.print((char) readBuf.get() + " ");
            }
            System.out.println();

            // 获取文件大小
            System.out.println("文件大小:" + channel.size() + " 字节");
        }
    }
}

四、Selector 选择器

Selector是NIO实现非阻塞IO的关键。它允许单个线程监控多个Channel,检测哪些Channel准备好进行IO操作。

4.1 网络编程中的Selector

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class SelectorServer {
    public static void main(String[] args) throws IOException {
        // 创建Selector
        Selector selector = Selector.open();

        // 创建ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8888));
        serverChannel.configureBlocking(false);  // 设置为非阻塞模式

        // 注册到Selector,监听OP_ACCEPT事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务器启动,监听端口8888...");

        while (true) {
            // select()阻塞,直到有事件就绪
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;

            // 获取就绪的事件集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectedKeys.iterator();

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

                if (key.isAcceptable()) {
                    // 处理新连接
                    handleAccept(key, selector);
                } else if (key.isReadable()) {
                    // 处理读事件
                    handleRead(key);
                }

                // 处理完必须移除,否则下次还会被处理
                iterator.remove();
            }
        }
    }

    private static void handleAccept(SelectionKey key, Selector selector)
            throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("新客户端连接:" + clientChannel.getRemoteAddress());
    }

    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        int bytesRead = clientChannel.read(buffer);
        if (bytesRead == -1) {
            // 客户端断开连接
            System.out.println("客户端断开:" + clientChannel.getRemoteAddress());
            clientChannel.close();
            return;
        }

        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());
        clientChannel.write(responseBuffer);
    }
}

4.2 NIO客户端示例

java 复制代码
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel channel = SocketChannel.open();
        channel.connect(new InetSocketAddress("localhost", 8888));

        System.out.println("已连接到服务器,请输入消息(输入exit退出):");

        try (Scanner scanner = new Scanner(System.in)) {
            while (true) {
                String input = scanner.nextLine();
                if ("exit".equalsIgnoreCase(input)) break;

                // 发送数据
                ByteBuffer writeBuffer = ByteBuffer.wrap(input.getBytes());
                channel.write(writeBuffer);

                // 接收数据
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int bytesRead = channel.read(readBuffer);
                if (bytesRead > 0) {
                    readBuffer.flip();
                    byte[] data = new byte[readBuffer.remaining()];
                    readBuffer.get(data);
                    System.out.println("服务器响应:" + new String(data));
                }
            }
        }
        channel.close();
    }
}

Selector事件类型

事件常量 说明
SelectionKey.OP_ACCEPT 服务器端接收客户端连接
SelectionKey.OP_CONNECT 客户端连接就绪
SelectionKey.OP_READ 数据可读
SelectionKey.OP_WRITE 数据可写

五、NIO三大组件关系总结

复制代码
┌──────────────────────────────────┐
│          Selector (选择器)         │
│  ┌────────┐  ┌────────┐         │
│  │ Key 1  │  │ Key 2  │  ...    │
│  └───┬────┘  └───┬────┘         │
└──────┼──────────┼───────────────┘
       │          │
   ┌───▼───┐  ┌───▼───┐
   │Channel│  │Channel│             数据源/目标
   └───┬───┘  └───┬───┘
       │          │
   ┌───▼───┐  ┌───▼───┐
   │Buffer │  │Buffer │             内存缓冲区
   └───────┘  └───────┘
  1. Buffer是数据容器,所有读写操作都要通过Buffer进行
  2. Channel是数据传输通道,连接数据源和目标
  3. Selector监控多个Channel的事件,实现单线程管理多连接

总结

本文详细讲解了Java NIO的三大核心组件:

  • Buffer:用capacity、limit、position、mark四个属性管理数据,flip()切换读写模式,clear()重置缓冲区
  • Channel:双向数据传输通道,FileChannel用于文件操作,SocketChannel用于网络通信,transferTo/transferFrom实现零拷贝
  • Selector:非阻塞IO的核心,通过select()多路复用监控多个Channel事件,极大提升高并发场景的性能

NIO是Netty、Mina等高性能网络框架的底层基础,也是构建高并发服务器不可或缺的技术栈。

✅ 亮点总结

  • Buffer四大属性(capacity/limit/position/mark)与状态切换操作:flip()写转读、clear()重置准备写、rewind()重读
  • FileChannel的零拷贝能力:transferTo()/transferFrom()利用OS的sendfile系统调用,数据不经过用户空间
  • Selector多路复用的核心机制:单个线程通过select()监控多个Channel的ACCEPT/READ/WRITE事件
  • 完整的NIO服务器-客户端实现示例,包含Selector事件循环、SelectionKey遍历移除的标准模式
  • 堆缓冲区(HeapByteBuffer)与直接缓冲区(DirectByteBuffer)的对比:后者减少一次拷贝但创建成本高

适用场景

  • 高并发网络服务器开发,如IM即时通讯系统、HTTP网关、推送服务
  • 大文件或大数据量的零拷贝传输,如文件服务器、流媒体分发
  • 游戏服务器后端架构,需要高效管理成千上万的并发玩家连接

扩展方向

  • 深入学习Netty框架的Reactor线程模型、Pipeline责任链模式及编解码器设计
  • 研究Java AIO(NIO.2异步IO)的CompletionHandler回调模型
  • 推荐阅读:25_Java反射机制入门

上一篇:Java序列化与反序列化 | 下一篇:Java反射机制入门

相关推荐
不懂的浪漫1 小时前
10|Netty native epoll 与零拷贝:从 Java NIO 再往下看一层![
java·netty·nio
摇滚侠1 小时前
Spring 零基础入门到进阶 入门 06-10
java·spring·intellij-idea
要开心吖ZSH1 小时前
AI医疗分诊与健康咨询助手agent开发——(1)从零搭建SpringBoot与AI对话系统:后端骨架 + 前端对话页 + SSE流式输出
java·ai·agent·健康医疗
biubiubiu07061 小时前
SpringBoot生产级日志配置
java·spring boot·后端
lie..1 小时前
基于大模型的智能客服系统部署与使用(二):接入前端可视化界面
人工智能·python
光影6272 小时前
Python接口自动化测试----Requests库基础入门
开发语言·python·测试工具·pycharm·自动化
程序媛_2 小时前
【Python】连接PostgreSQL获取手机验证码
开发语言·python·postgresql
ch.ju2 小时前
Java Programming Chapter 4——Inherited call
java·开发语言
Kobebryant-Manba2 小时前
学习参数管理
pytorch·python·深度学习