当前位置: 首页 > article >正文

【面试】Java 中的 BIO、NIO 和 AIO:区别、使用及实例

      在 Java 的 I/O 编程领域,BIO、NIO 和 AIO 是三种重要的 I/O 模型,它们各自有着独特的特点和适用场景。理解这三种模型的区别,对于编写高效、高性能的 Java 网络应用程序至关重要。

一、区别对比

BIO (Block IO)

NIO (New IO)

AIO(Asynchronous I/O)

JDK 版本

所有版本

JDK1.4 及之后

JDK1.7 及之后

异步 / 阻塞

同步阻塞。一个连接一个线程。线程发起 IO 请求,不管内核是否准备好 IO 操作,从发起请求起,线程一直阻塞,直到操作完成。数据的读取写入必须阻塞在一个线程内等待其完成。

同步阻塞 / 非阻塞。一个请求一个线程。客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。用户进程也需要时不时的询问 IO 操作是否就绪,这要求用户进程不停的去询问。

异步非阻塞。 一个有效请求一个线程。用户进程只需要发起一个 IO 操作然后立即返回,等 IO 操作真正的完成以后,应用程序会得到 IO 操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的 IO 读写操作,因为真正的 IO 读取或者写入操作已经由内核完成了。

使用场景

适用于传统的、并发连接数较少且对性能要求不高的场景

已成为解决高并发与大量连接、I/O 处理问题的有效方式。比如:Netty、多人聊天室。

适用于连接数目多且连接比较长(重操作)的架构。例如:相册服务器。目前 AIO 的应用还不是很广泛。

二、为什么 Netty 用 NIO?

Netty 之前也尝试使用过 AIO,不过又放弃了,因为 AIO 在性能、内存占用上,实际不如 NIO。Netty 作者的原话如下:

  • Not faster than NIO (epoll) on unix systems (which is true) 。(在 UNIX 系统上不比 NIO 快)
  • There is no daragream support (不支持数据报)
  • Unnecessary threading model (too much abstraction without usage) (不必要的线程模型)

详细分析:

  • Linux 上,AIO 底层实现仍使用 Epoll,没有很好的实现 AIO,因此性能上没有明显优势,而且被 JDK 封装了一层不容易优化。
  • Netty 整体架构是基本 reactor 模型,而 AIO 是 proactor 模型,混合在一起会比较混乱。
  • AIO 有个缺点:接收数据需要预先分配缓冲区,而不是 NIO 那种需要接收时才需要分配缓存,所以对连接数量非常大但流量小的情况,会浪费内存。
  • Linux 上 AIO 不够成熟,处理回调的结果速度跟不上处理需求,供不应求,造成处理速度有瓶颈。比如:外卖员太少,顾客太多。

另外:NIO 是一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存(区别于 JVM 的运行时数据区),然后通过一个存储在 java 堆里面的 DirectByteBuffer 对象作为这块内存的直接引用进行操作。这样能在一些场景显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据

三、BIO(Blocking I/O) - 阻塞式 I/O

1. 概念

BIO 是 Java 早期的 I/O 模型。在 BIO 中,当一个线程调用read()或write()方法时,该线程会被阻塞,直到数据被完全读取或写入。这意味着在 I/O 操作进行期间,线程无法执行其他任务,严重影响了程序的并发处理能力。

2. 使用场景

BIO 适用于连接数目比较小且固定的架构,这种场景下编程简单,代码容易理解。例如一些传统的企业级应用,其并发连接数相对较少,对性能要求不是特别高。

3. 实例代码

下面是一个简单的 BIO 服务器端示例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class BIOServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(8080)) {
            System.out.println("Server started on port 8080");
            while (true) {
                try (Socket clientSocket = serverSocket.accept()) {
                    System.out.println("Client connected: " + clientSocket);
                    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                    PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
                    String inputLine;
                    while ((inputLine = in.readLine()) != null) {
                        System.out.println("Received from client: " + inputLine);
                        out.println("Server response: " + inputLine);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,serverSocket.accept()方法会阻塞,直到有客户端连接。in.readLine()和out.println()也会阻塞线程,直到数据读取或写入完成。

四、NIO(New I/O) - 非阻塞式 I/O

1. 概念

NIO 是 Java 1.4 引入的新的 I/O 模型。它提供了基于通道(Channel)和缓冲区(Buffer)的 I/O 操作方式。NIO 的主要特点是非阻塞,线程可以在 I/O 操作未完成时继续执行其他任务。通过 Selector(选择器),一个线程可以管理多个通道,大大提高了并发处理能力。

2. 使用场景

NIO 适用于连接数目多且连接比较短(轻操作)的架构,例如聊天服务器、Web 服务器等需要处理大量并发请求的场景。

3. 实例代码

下面是一个简单的 NIO 服务器端示例:

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 NIOServer {
    private static final int PORT = 8080;
    private Selector selector;

    public NIOServer() throws IOException {
        selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server started on port " + PORT);
    }

    public void listen() {
        try {
            while (true) {
                selector.select();
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (key.isAcceptable()) {
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                        SocketChannel clientSocketChannel = serverSocketChannel.accept();
                        clientSocketChannel.configureBlocking(false);
                        clientSocketChannel.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        SocketChannel clientSocketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = clientSocketChannel.read(buffer);
                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            String message = new String(data);
                            System.out.println("Received from client: " + message);
                            ByteBuffer responseBuffer = ByteBuffer.wrap(("Server response: " + message).getBytes());
                            clientSocketChannel.write(responseBuffer);
                        }
                    }
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            NIOServer server = new NIOServer();
            server.listen();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,ServerSocketChannel和SocketChannel都设置为非阻塞模式。通过Selector监听不同的事件(如OP_ACCEPT和OP_READ),线程可以在多个通道之间切换,而不是阻塞在单个 I/O 操作上。

五、AIO(Asynchronous I/O) - 异步式 I/O

1. 概念

AIO 是 Java 7 引入的异步 I/O 模型,也被称为 NIO.2。与 NIO 相比,AIO 更加注重异步操作。在 AIO 中,I/O 操作是异步完成的,当一个 I/O 操作发起后,线程无需等待操作完成,可以继续执行其他任务。操作完成后,系统会通过回调函数通知线程。

2. 使用场景

AIO 适用于连接数目多且连接比较长(重操作)的架构,例如文件服务器、大数据处理等对 I/O 性能要求极高的场景。

3. 实例代码

下面是一个简单的 AIO 服务器端示例:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;

public class AIOServer {
    private static final int PORT = 8080;
    private AsynchronousServerSocketChannel serverSocketChannel;

    public AIOServer() throws IOException {
        serverSocketChannel = AsynchronousServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(PORT));
        System.out.println("Server started on port " + PORT);
    }

    public void listen() {
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
            @Override
            public void completed(AsynchronousSocketChannel clientSocketChannel, Void attachment) {
                try {
                    serverSocketChannel.accept(null, this);
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    clientSocketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            if (result > 0) {
                                buffer.flip();
                                byte[] data = new byte[buffer.remaining()];
                                buffer.get(data);
                                String message = new String(data);
                                System.out.println("Received from client: " + message);
                                ByteBuffer responseBuffer = ByteBuffer.wrap(("Server response: " + message).getBytes());
                                clientSocketChannel.write(responseBuffer, responseBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                                    @Override
                                    public void completed(Integer result, ByteBuffer buffer) {
                                        try {
                                            clientSocketChannel.close();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }

                                    @Override
                                    public void failed(Throwable exc, ByteBuffer attachment) {
                                        try {
                                            clientSocketChannel.close();
                                        } catch (IOException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                });
                            }
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            try {
                                clientSocketChannel.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Void attachment) {
                exc.printStackTrace();
            }
        });
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            AIOServer server = new AIOServer();
            server.listen();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,AsynchronousServerSocketChannel和AsynchronousSocketChannel都是异步的。通过CompletionHandler处理 I/O 操作的完成事件,线程在发起 I/O 操作后可以立即返回,继续执行其他任务。

六、BIO、NIO 和 AIO 的区别总结

  1. 阻塞方式
    • BIO 是阻塞式 I/O,线程在 I/O 操作时会被阻塞。
    • NIO 是非阻塞式 I/O,线程在 I/O 操作未完成时可以继续执行其他任务。
    • AIO 是异步式 I/O,I/O 操作完全异步,操作完成后通过回调通知线程。
  1. 编程模型
    • BIO 基于流(Stream)进行操作,代码简单直观。
    • NIO 基于通道(Channel)和缓冲区(Buffer),通过 Selector 管理多个通道,编程相对复杂。
    • AIO 同样基于通道和缓冲区,使用异步回调机制,编程模型更加复杂。
  1. 适用场景
    • BIO 适用于并发连接数少且对性能要求不高的场景。
    • NIO 适用于高并发、轻量级 I/O 操作的场景。
    • AIO 适用于高并发、重量级 I/O 操作的场景。

        通过对 BIO、NIO 和 AIO 的学习和对比,开发者可以根据具体的应用场景选择最合适的 I/O 模型,从而编写出高效、高性能的 Java 应用程序


http://www.kler.cn/a/569025.html

相关文章:

  • 在笔记本电脑上用DeepSeek搭建个人知识库
  • Tomcat原理之HTTP协议:从寻址到会话管理的全链路解析
  • Nginx+PHP+MYSQL-Ubuntu在线安装
  • v-model=‘xxx‘和v-model:visible=‘xxx‘有什么区别
  • 授权与认证之jwt(一)创建Jwt工具类
  • 【音视频】编解码相关概念总结
  • 命令行方式安装KFS同步KES到KADB
  • python基于后门的神经网络模型水印通用方法
  • 【软件测试】_使用selenium进行自动化测试示例
  • 在QML中注册C++类型
  • 通过logback日志简单实现链路追踪
  • 总结前端常用数据结构 之 队列篇【JavaScript 】
  • yolov8,yolo11,yolo12 服务器训练到部署全流程 笔记
  • hive 面试题
  • Linux 检测内存泄漏方法总结
  • 【漫话机器学习系列】113.逻辑回归(Logistic Regression) VS 线性回归(Linear Regression)
  • JPA属性转换器的使用与实例解析
  • 3-5 WPS JS宏 工作表的移动与复制学习笔记
  • Vue3生命周期以及与Vue2的区别
  • 面试基础--JVM垃圾回收深度剖析(JDK8)