【面试】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 应用程序

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/980623.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Maven】基于IDEA进行Maven工程的创建、构建

文章目录 一、基于IDEA创建Maven工程1. 概念梳理Maven工程的GAVP2. Idea构建Maven Java SE工程3. Idea构建Maven Java Web工程3.1 创建一个maven的javase工程3.2 修改pom.xml文件打包方式3.3 设置web资源路径和web.xml路径 4. Maven工程项目结构说明 二、基于IDEA进行Maven工程…

计算机毕业设计SpringBoot+Vue.js在线课程管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【爬虫基础】第二部分 爬虫基础理论 P3/3

上节内容回顾&#xff1a;【爬虫基础】第一部分 网络通讯 P1/3-CSDN博客 【爬虫基础】第一部分 网络通讯-Socket套接字 P2/3-CSDN博客 【爬虫基础】第一部分 网络通讯-编程 P3/3-CSDN博客 【爬虫基础】第二部分 爬虫基础理论 P1/3-CSDN博客 【爬虫基础】第二部分 爬虫基础理论…

【子网掩码计算器:Python + Tkinter 实现】

子网掩码计算器&#xff1a;Python Tkinter 实现 引言代码功能概述代码实现思路1. 界面设计2. 功能实现3. 事件处理 子网掩码计算器实现步骤1. 导入必要的库2. 定义主窗口类 SubnetCalculatorApp3. 创建菜单栏4. 创建界面组件5. 判断 IP 地址类别6. 计算子网信息7. 其他功能函…

【第十节】C++设计模式(结构型模式)-Flyweight( 享元)模式

目录 一、问题背景 二、模式选择 三、代码实现 四、总结讨论 一、问题背景 享元模式&#xff08;Flyweight Pattern&#xff09;在对象存储优化中的应用 在面向对象系统的设计与实现中&#xff0c;创建对象是最常见的操作之一。然而&#xff0c;如果一个应用程序使用了过多…

macOS - 使用 tmux

文章目录 安装 tmux使用更多快捷键说明 安装 tmux brew install tmux使用 在终端输入 tmux 进入 tmux 界面&#xff0c;然后 输入 Control Option B 进入交互模式 输入 % 左右分栏&#xff0c;" 上下分割 上一个窗格&#xff1a;{&#xff0c;下一个&#xff1a;} PS…

【洛谷贪心算法题】P1094纪念品分组

该题运用贪心算法&#xff0c;核心思想是在每次分组时&#xff0c;尽可能让价格较小和较大的纪念品组合在一起&#xff0c;以达到最少分组的目的。 【算法思路】 输入处理&#xff1a;首先读取纪念品的数量n和价格上限w&#xff0c;然后依次读取每件纪念品的价格&#xff0c;…

16. LangChain实战项目2——易速鲜花内部问答系统

需求简介 易束鲜花企业内部知识库如下&#xff1a; 本实战项目设计一个内部问答系统&#xff0c;基于这些内部知识&#xff0c;回答内部员工的提问。 在前面课程的基础上&#xff0c;需要安装的依赖包如下&#xff1a; pip install docx2txt pip install qdrant-client pip i…

Minio搭建并在SpringBoot中使用完成用户头像的上传

Minio使用搭建并上传用户头像到服务器操作,学习笔记 Minio介绍 minio官网 MinIO是一个开源的分布式对象存储服务器&#xff0c;支持S3协议并且可以在多节点上实现数据的高可用和容错。它采用Go语言开发&#xff0c;拥有轻量级、高性能、易部署等特点&#xff0c;并且可以自由…

Spring AI:让AI应用开发更简单

文章目录 引言什么是Spring AI&#xff1f;核心特性 Spring AI的核心组件ChatClient&#xff1a;聊天模型示例代码图示 ImageClient&#xff1a;图像生成示例代码图示 Prompt Templates&#xff1a;提示词模板示例代码 Spring AI的优势示例项目&#xff1a;智能机票助手代码实现…

【C】链式二叉树算法题1 -- 单值二叉树

leetcode链接https://leetcode.cn/problems/univalued-binary-tree/description/ 1 题目描述 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1…

什么是最终一致性,它对后端系统的意义是什么

最终一致性(Eventual Consistency)是分布式系统中的一种一致性模型。与传统的强一致性模型不同,最终一致性并不要求系统在任何时刻都保持一致,而是保证在足够的时间后,所有节点的数据最终会达到一致的状态。换句话说,系统允许短时间内出现数据的不一致性,但最终会通过某…

掌握大模型高效任务流搭建(一):构建LangChain任务流

前言&#xff1a; 在LangChain框架中&#xff0c;“链”占据着核心地位。它允许我们将众多任务模块串联起来&#xff0c;构建出富有弹性的任务流。借助这种链式结构&#xff0c;我们能够处理复杂的逻辑&#xff0c;并实现任务的自动化。在实际场景里&#xff0c;链式操作极大地…

目标检测——数据处理

1. Mosaic 数据增强 Mosaic 数据增强步骤: (1). 选择四个图像&#xff1a; 从数据集中随机选择四张图像。这四张图像是用来组合成一个新图像的基础。 (2) 确定拼接位置&#xff1a; 设计一个新的画布(输入size的2倍)&#xff0c;在指定范围内找出一个随机点&#xff08;如…

塑造网络安全的关键事件

注&#xff1a;本文为 “网络安全” 相关文章合辑。 机翻&#xff0c;未校。 Timeline of Cyber Security: Key Events that Shaped the Field 网络安全时间表&#xff1a;塑造该领域的关键事件 October 29, 2023 Cyberattacks are an everyday threat, always changing. T…

题解 | 牛客周赛82 Java ABCDEF

目录 题目地址 做题情况 A 题 B 题 C 题 D 题 E 题 F 题 牛客竞赛主页 题目地址 牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ 做题情况 A 题 判断字符串第一个字符和第三个字符是否相等 import java.io.*; import java.math.*; import java.u…

Redis 高可用性:如何让你的缓存一直在线,稳定运行?

&#x1f3af; 引言&#xff1a;Redis的高可用性为啥这么重要&#xff1f; 在现代高可用系统中&#xff0c;Redis 是一款不可或缺的分布式缓存与数据库系统。无论是提升访问速度&#xff0c;还是实现数据的高效持久化&#xff0c;Redis 都能轻松搞定。可是&#xff0c;当你把 …

uniapp-原生android插件开发摘要

uni-app在App侧的原生扩展插件&#xff0c;支持使用java、object-c等原生语言编写&#xff0c;从HBuilderX 3.6起&#xff0c;新增支持了使用uts来开发原生插件。 基础项目 UniPlugin-Hello-AS工程请在App离线SDK中查找 基础项目(App离线SDK)已经配置好了自定义插件所需要的…

【定昌Linux系统】部署了java程序,设置开启启动

将代码上传到相应的目录&#xff0c;并且配置了一个.sh的启动脚本文件 文件内容&#xff1a; #!/bin/bash# 指定JAR文件的路径&#xff08;如果JAR文件在当前目录&#xff0c;可以直接使用文件名&#xff09; JAR_FILE"/usr/local/java/xs_luruan_client/lib/xs_luruan_…

SpringBoot源码解析(十):应用上下文AnnotationConfigServletWebServerApplicationContext构造方法

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args Sp…