主页 > 人工智能  > 

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

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

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

标签:

【面试】Java中的BIO、NIO和AIO:区别、使用及实例由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【面试】Java中的BIO、NIO和AIO:区别、使用及实例