Skip to content

BIO、NIO与AIO核心原理

IO概念详解

什么是IO?

IO(Input/Output)即输入/输出,是计算机系统与外部世界交互的基础。

从计算机结构角度理解IO:

根据冯·诺依曼体系结构,计算机由五大部分组成:运算器、控制器、存储器、输入设备、输出设备。

mermaid
graph TB
    A[中央处理器CPU] --> B[运算器<br/>ALU]
    A --> C[控制器<br/>CU]
    D[存储器<br/>Memory] --> E[内存RAM]
    D --> F[硬盘/SSD]
    G[输入设备] --> H[键盘/鼠标]
    G --> I[网卡接收]
    J[输出设备] --> K[显示器]
    J --> L[网卡发送]
    
    style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style D fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10
    style G fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style J fill:#9B59B6,stroke:#6C3483,stroke-width:2px,rx:10,ry:10

输入设备(如键盘、网卡)向计算机输入数据,输出设备(如显示器、网卡)接收计算机输出的数据。从计算机结构视角看,IO描述了计算机系统与外部设备之间的通信过程。

从应用程序角度理解IO:

操作系统为了安全和稳定性,将进程的地址空间划分为用户空间(User Space)内核空间(Kernel Space)

  • 用户空间:运行应用程序代码,权限受限
  • 内核空间:运行操作系统内核代码,拥有访问硬件的特权

应用程序无法直接访问硬件设备,必须通过**系统调用(System Call)**请求内核代间完成IO操作。

mermaid
graph TB
    A[应用程序<br/>用户空间] -->|系统调用| B[操作系统内核<br/>内核空间]
    B -->|驱动程序| C[硬件设备<br/>磁盘/网卡]
    C -->|数据| B
    B -->|拷贝数据| A
    
    style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style B fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style C fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10

从应用程序视角看,IO是应用程序对内核发起调用,由内核执行具体IO操作的过程。

我们在开发中最常接触的是磁盘IO(文件读写)网络IO(网络通信)

IO操作的两个阶段

当应用程序发起IO调用后,会经历两个关键阶段:

  1. 等待数据准备(Waiting for Data): 内核等待IO设备准备好数据
  2. 数据拷贝(Copying Data): 内核将数据从内核空间拷贝到用户空间

不同的IO模型,这两个阶段的阻塞/非阻塞特性不同。

操作系统的五种IO模型

UNIX系统定义了5种IO模型:

  1. 同步阻塞IO (Blocking IO)
  2. 同步非阻塞IO (Non-blocking IO)
  3. IO多路复用 (IO Multiplexing)
  4. 信号驱动IO (Signal Driven IO)
  5. 异步IO (Asynchronous IO)

同步阻塞IO模型

从系统调用到数据拷贝完成,进程全程阻塞等待。

场景类比:小王去银行办理业务,取号排队后,一直在柜台前等待,直到业务办理完成才离开。

mermaid
sequenceDiagram
    participant App as 应用程序
    participant Kernel as 内核
    participant Device as IO设备
    
    App->>Kernel: read系统调用
    Note over App: 应用程序阻塞
    Kernel->>Device: 等待数据准备
    Note over Kernel: 内核阻塞等待
    Device->>Kernel: 数据准备就绪
    Kernel->>Kernel: 拷贝数据到内核缓冲区
    Kernel->>App: 数据拷贝到用户空间
    Note over App: 返回结果,解除阻塞

同步非阻塞IO模型

应用程序不断轮询检查数据是否准备好,数据拷贝阶段仍然阻塞。

场景类比:小王去银行办理业务,取号后不在柜台前等待,而是去附近逛街,每隔几分钟回来看一次是否轮到自己,轮到后站在柜台前等待业务办完。

mermaid
sequenceDiagram
    participant App as 应用程序
    participant Kernel as 内核
    participant Device as IO设备
    
    App->>Kernel: read调用(非阻塞)
    Kernel-->>App: 返回EWOULDBLOCK
    Note over App: 继续其他工作
    
    App->>Kernel: read调用(轮询)
    Kernel-->>App: 返回EWOULDBLOCK
    Note over App: 继续其他工作
    
    App->>Kernel: read调用(轮询)
    Note over Kernel: 数据已就绪
    Kernel->>App: 数据拷贝到用户空间
    Note over App: 阻塞等待拷贝完成

优点: 利用轮询间隙可以处理其他任务
缺点: 频繁的系统调用浪费CPU资源

IO多路复用模型

使用select/poll/epoll等机制,一个线程可以监控多个IO连接,当某个连接数据就绪时才发起read调用。

场景类比:小王请银行大堂经理帮忙关注所有柜台,当有空闲柜台时通知他。小王可以安心逛街,接到通知后再去办理业务。

mermaid
sequenceDiagram
    participant App as 应用程序
    participant Select as select/epoll
    participant Kernel as 内核
    participant Device as IO设备
    
    App->>Select: 注册多个文件描述符
    Note over App: 阻塞在select调用上
    Device->>Kernel: 数据1准备就绪
    Device->>Kernel: 数据2准备就绪
    Select-->>App: 返回就绪的文件描述符列表
    App->>Kernel: read(fd1)
    Kernel->>App: 返回数据1
    App->>Kernel: read(fd2)
    Kernel->>App: 返回数据2

优点: 单线程可以处理多个并发连接,减少线程开销
核心思想: 通过select/poll/epoll减少无效的系统调用

信号驱动IO模型

应用程序注册信号处理函数,当数据准备好时,内核发送信号通知应用程序。

场景类比:小王是银行VVIP客户,告诉大堂经理当柜台准备好时打电话通知他,然后安心逛街。接到电话后赶回银行办理业务。

mermaid
sequenceDiagram
    participant App as 应用程序
    participant Kernel as 内核
    participant Device as IO设备
    
    App->>Kernel: 注册SIGIO信号处理函数
    Note over App: 继续其他工作(非阻塞)
    Device->>Kernel: 数据准备就绪
    Kernel->>App: 发送SIGIO信号
    App->>Kernel: 信号处理函数中调用read
    Note over App: 阻塞等待数据拷贝
    Kernel->>App: 数据拷贝完成,返回结果

特点: 等待数据阶段非阻塞,数据拷贝阶段阻塞

异步IO模型

整个IO操作都由内核完成,完成后通知应用程序。

场景类比:小王请银行的专属客户经理全权代办业务,自己该干嘛干嘛,业务办完后客户经理直接把结果送到他手上。

mermaid
sequenceDiagram
    participant App as 应用程序
    participant Kernel as 内核
    participant Device as IO设备
    
    App->>Kernel: aio_read异步读请求
    Note over App: 立即返回,继续其他工作
    Kernel->>Device: 等待数据准备
    Device->>Kernel: 数据准备就绪
    Kernel->>Kernel: 拷贝数据到用户空间
    Kernel->>App: 通知IO操作完成
    Note over App: 直接使用数据

特点: 全程非阻塞,由内核负责数据准备和拷贝

五种模型对比总结

IO模型等待数据阶段数据拷贝阶段典型场景
同步阻塞IO阻塞阻塞连接数少的简单应用
同步非阻塞IO非阻塞(轮询)阻塞需要及时响应的场景
IO多路复用阻塞(select)阻塞高并发服务器(Nginx/Redis)
信号驱动IO非阻塞阻塞实时性要求高的场景
异步IO非阻塞非阻塞高性能IO密集型应用

前四种都是同步IO,因为数据拷贝阶段应用程序都需要等待。只有异步IO是真正的异步,全程无需等待。

Java中的三种IO模型

BIO - 同步阻塞IO

BIO(Blocking IO)是Java最传统的IO模型,对应操作系统的同步阻塞IO

核心特点

  • 每个连接分配一个独立线程处理
  • 线程在read/write调用时会阻塞,直到IO操作完成
  • 适合连接数少的场景

工作原理

mermaid
graph TB
    A[客户端连接1] --> B[线程1]
    C[客户端连接2] --> D[线程2]
    E[客户端连接3] --> F[线程3]
    G[客户端连接N] --> H[线程N]
    
    B --> I[read阻塞]
    D --> J[read阻塞]
    F --> K[read阻塞]
    H --> L[read阻塞]
    
    style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style C fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style E fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style G fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style I fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style J fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style K fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style L fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10

代码示例

java
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动在端口 8080");
        
        while (true) {
            // accept()阻塞,直到有客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("新客户端连接: " + socket.getRemoteSocketAddress());
            
            // 为每个连接创建新线程处理
            new Thread(() -> {
                try {
                    InputStream is = socket.getInputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    // read()阻塞,直到读取到数据
                    while ((len = is.read(buffer)) != -1) {
                        String message = new String(buffer, 0, len);
                        System.out.println("收到消息: " + message);
                        
                        // 回复客户端
                        OutputStream os = socket.getOutputStream();
                        os.write(("服务器收到: " + message).getBytes());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

性能瓶颈

面对万级并发连接时,BIO存在严重问题:

  • 线程开销: 每个连接一个线程,创建和切换开销巨大
  • 内存消耗: 每个线程占用1MB左右的栈空间,1万个连接需要约10GB内存
  • CPU浪费: 大量线程阻塞在IO上,无法充分利用CPU

NIO - 同步非阻塞IO

NIO(New IO / Non-blocking IO)在Java 1.4引入,对应操作系统的IO多路复用模型

核心特点

  • 基于Channel(通道)和Buffer(缓冲区)
  • 使用Selector(选择器)实现单线程管理多个连接
  • 面向缓冲区,减少系统调用次数

工作原理

mermaid
graph TB
    A[客户端连接1] --> E[Selector<br/>选择器]
    B[客户端连接2] --> E
    C[客户端连接3] --> E
    D[客户端连接N] --> E
    E --> F[单个线程<br/>处理所有就绪事件]
    
    F --> G[读事件处理]
    F --> H[写事件处理]
    F --> I[连接事件处理]
    
    style E fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style F fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10

核心组件

  1. Channel(通道): 双向数据传输通道
    • FileChannel: 文件IO
    • SocketChannel: TCP客户端
    • ServerSocketChannel: TCP服务端
    • DatagramChannel: UDP通信
  2. Buffer(缓冲区): 数据容器
    • ByteBuffer: 字节缓冲
    • CharBuffer: 字符缓冲
    • IntBuffer: 整型缓冲
    • ...
  3. Selector(选择器): 多路复用器
    • 监听OP_ACCEPT: 接收连接事件
    • 监听OP_CONNECT: 连接就绪事件
    • 监听OP_READ: 读就绪事件
    • 监听OP_WRITE: 写就绪事件

代码示例

java
public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建选择器
        Selector selector = Selector.open();
        
        // 创建ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false); // 设置非阻塞模式
        
        // 注册到选择器,监听连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO服务器启动在端口 8080");
        
        while (true) {
            // 阻塞,直到有事件就绪
            selector.select();
            
            // 遍历所有就绪的事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                
                if (key.isAcceptable()) {
                    // 处理连接事件
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel client = server.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                    System.out.println("新客户端连接: " + client.getRemoteAddress());
                    
                } else if (key.isReadable()) {
                    // 处理读事件
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int len = client.read(buffer);
                    
                    if (len > 0) {
                        buffer.flip();
                        String message = new String(buffer.array(), 0, len);
                        System.out.println("收到消息: " + message);
                        
                        // 切换到写事件
                        client.register(selector, SelectionKey.OP_WRITE);
                    } else if (len == -1) {
                        client.close();
                    }
                    
                } else if (key.isWritable()) {
                    // 处理写事件
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.wrap("服务器收到消息".getBytes());
                    client.write(buffer);
                    
                    // 切换回读事件
                    client.register(selector, SelectionKey.OP_READ);
                }
            }
        }
    }
}

性能优势

  • 减少线程: 单线程处理数千个连接
  • 降低内存: 不需要为每个连接分配线程栈
  • 提高吞吐: 减少上下文切换,充分利用CPU

AIO - 异步IO

AIO(Asynchronous IO)在Java 7引入,又称NIO 2.0,对应操作系统的异步IO模型

核心特点

  • 基于事件和回调机制
  • IO操作立即返回,操作系统负责完成IO
  • 操作完成后通过回调函数通知应用程序

工作原理

mermaid
graph TB
    A[应用程序] -->|aio_read| B[操作系统内核]
    A -->|立即返回| C[继续其他工作]
    B --> D[等待数据准备]
    B --> E[拷贝数据到用户空间]
    E -->|回调通知| A
    
    style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style B fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
    style C fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10

代码示例

java
public class AIOServer {
    public static void main(String[] args) throws Exception {
        // 创建异步ServerSocketChannel
        AsynchronousServerSocketChannel serverChannel = 
            AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(8080));
        
        System.out.println("AIO服务器启动在端口 8080");
        
        // 接收连接(异步操作)
        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                // 继续接收下一个连接
                serverChannel.accept(null, this);
                
                // 处理当前连接
                System.out.println("新客户端连接");
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                // 异步读取数据
                client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer len, ByteBuffer buf) {
                        if (len > 0) {
                            buf.flip();
                            String message = new String(buf.array(), 0, len);
                            System.out.println("收到消息: " + message);
                            
                            // 异步写回数据
                            ByteBuffer response = ByteBuffer.wrap(("服务器收到: " + message).getBytes());
                            client.write(response, response, new CompletionHandler<Integer, ByteBuffer>() {
                                @Override
                                public void completed(Integer result, ByteBuffer attachment) {
                                    System.out.println("响应发送成功");
                                }
                                
                                @Override
                                public void failed(Throwable exc, ByteBuffer attachment) {
                                    exc.printStackTrace();
                                }
                            });
                        }
                    }
                    
                    @Override
                    public void failed(Throwable exc, ByteBuffer buf) {
                        exc.printStackTrace();
                    }
                });
            }
            
            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });
        
        // 阻止主线程退出
        Thread.sleep(Integer.MAX_VALUE);
    }
}

AIO的局限性

虽然AIO理论上性能最优,但在Java生态中应用并不广泛:

  1. Linux支持不完善: Linux的AIO实现(epoll)性能提升不明显
  2. 编程复杂度高: 回调嵌套导致代码难以维护
  3. 框架选择: Netty曾尝试AIO,后来又放弃了

三种模型对比

mermaid
graph TB
    A[BIO<br/>同步阻塞IO] --> D[线程阻塞<br/>一连接一线程]
    B[NIO<br/>同步非阻塞IO] --> E[Selector轮询<br/>一线程多连接]
    C[AIO<br/>异步IO] --> F[回调通知<br/>内核完成IO]
    
    D --> G{适用场景}
    E --> G
    F --> G
    
    G -->|连接少| H[BIO简单直观]
    G -->|高并发| I[NIO高性能]
    G -->|IO密集| J[AIO理论最优]
    
    style A fill:#4A90E2,stroke:#2E5C8A,stroke-width:2px,rx:10,ry:10
    style B fill:#50C878,stroke:#2E7D4E,stroke-width:2px,rx:10,ry:10
    style C fill:#E94B3C,stroke:#B8341F,stroke-width:2px,rx:10,ry:10
特性BIONIOAIO
IO类型同步阻塞同步非阻塞(IO多路复用)异步非阻塞
编程模型简单直观复杂回调复杂
连接数少量(几百)大量(几千到几万)大量
适用场景连接少且固定高并发轻操作(聊天室)高并发重操作(文件服务器)
引入版本JDK 1.0JDK 1.4JDK 1.7
典型应用早期Web服务器Netty/Tomcat NIO少见

总结

本文深入讲解了IO的核心概念和Java中的三种IO模型:

  1. IO本质: 应用程序通过系统调用请求内核完成数据传输
  2. 五种OS模型: 从同步阻塞到异步IO,逐步提升并发性能
  3. BIO: 简单但性能受限于线程数量
  4. NIO: 通过IO多路复用实现高并发,是Java服务器的主流选择
  5. AIO: 理论最优但实际应用受限

在实际项目中,建议:

  • 低并发场景: 使用BIO,代码简单易维护
  • 高并发场景: 使用NIO框架(如Netty),性能和稳定性兼顾
  • 特殊IO密集场景: 可以尝试AIO,但要权衡编程复杂度

理解这些IO模型的原理,能帮助我们在不同场景下选择最合适的技术方案。

更新: 2025-12-04 17:35:10
原文: https://www.yuque.com/u22210564/zoxfmt/doc-04-io-04

Java 后端面试知识库