为什么Java NIO就是比BIO高效呢?【内含对比代码】

为什么Java NIO就是比BIO高效呢?【内含对比代码】

为什么 Java NIO 比 Java BIO 高效?

非阻塞 I/O:

BIO:每个 I/O 操作都会阻塞线程,导致大量线程处于空闲状态,增加了线程管理和上下文切换的开销。

NIO:I/O 操作是非阻塞的,线程可以在等待 I/O 完成的同时执行其他任务,减少了线程阻塞的时间,提高了资源利用率。

多路复用(Multiplexing):

BIO:每个连接对应一个线程,随着连接数的增加,线程数量也线性增长,资源消耗大。

NIO:使用 Selector 可以同时监听多个通道,单个线程可以管理多个连接,减少了线程数量,降低了资源消耗。

缓冲区(Buffer):

BIO:数据以字节流形式逐字节或逐块传输,频繁的系统调用导致性能下降。

NIO:数据存储在缓冲区中,可以通过一次系统调用来批量读取或写入数据,减少了系统调用次数,提高了 I/O 效率。

直接缓冲区(Direct Buffer):

BIO:数据需要在 JVM 堆内存和操作系统内核空间之间多次复制,增加了数据复制的开销。

NIO:直接缓冲区直接在 JVM 堆外内存中分配,避免了数据在 JVM 堆内存和操作系统内核空间之间的复制,减少了数据复制的开销,提高了 I/O 操作的速度。

通道(Channel):

BIO:数据通过 InputStream 和 OutputStream 流进行传输,每次操作只能处理一部分数据,代码复杂且效率低下。

NIO:Channel 是双向的,既可以读取数据也可以写入数据,简化了数据处理流程,减少了代码复杂度。

对比案例

BIO 示例:简单 TCP 服务器

服务器端代码:

package com.example.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;

publicclass BIOServer {

public static void main(String[] args) throws IOException {

// 创建一个 ServerSocket 监听 8080 端口

ServerSocket serverSocket = new ServerSocket(8080);

System.out.println("BIO Server started on port 8080");

while (true) {

// 阻塞等待客户端连接

Socket clientSocket = serverSocket.accept();

System.out.println("New client connected: " + clientSocket.getInetAddress());

// 为每个客户端连接创建一个新的线程来处理

new Thread(new ClientHandler(clientSocket)).start();

}

}

staticclass ClientHandler implements Runnable {

privatefinal Socket clientSocket;

public ClientHandler(Socket socket) {

this.clientSocket = socket;

}

@Override

public void run() {

try (

// 创建输入流读取客户端发送的数据

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("Echo: " + inputLine);

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

// 关闭客户端连接

clientSocket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

}

客户端代码:

package com.example.bio;

import java.io.*;

import java.net.Socket;

publicclass BIOClient {

public static void main(String[] args) throws IOException {

// 连接到服务器的 8080 端口

Socket socket = new Socket("localhost", 8080);

System.out.println("Connected to server");

// 创建标准输入流读取用户输入

BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));

// 创建输入流读取服务器发送的数据

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

// 创建输出流向服务器发送数据

PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

String userInputLine;

// 循环读取用户输入并发送到服务器

while ((userInputLine = userInput.readLine()) != null) {

out.println(userInputLine);

// 读取服务器的响应并打印

System.out.println("Server response: " + in.readLine());

}

// 关闭连接

socket.close();

}

}

NIO 示例:简单 TCP 服务器

服务器端代码:

package com.example.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;

publicclass NIOServer {

public static void main(String[] args) throws IOException {

// 创建一个 Selector 来管理多个 Channel

Selector selector = Selector.open();

// 创建一个 ServerSocketChannel 监听 8080 端口

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.bind(new InetSocketAddress(8080));

// 设置为非阻塞模式

serverSocketChannel.configureBlocking(false);

// 注册 OP_ACCEPT 事件,表示关注新的连接请求

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

System.out.println("NIO Server started on port 8080");

while (true) {

// 阻塞直到至少一个事件发生

selector.select();

// 获取所有准备就绪的 SelectionKey

Set selectedKeys = selector.selectedKeys();

Iterator keyIterator = selectedKeys.iterator();

while (keyIterator.hasNext()) {

SelectionKey key = keyIterator.next();

if (key.isAcceptable()) {

// 处理新的连接请求

handleAccept(key, selector);

} elseif (key.isReadable()) {

// 处理可读事件

handleRead(key);

}

// 移除已经处理过的 SelectionKey

keyIterator.remove();

}

}

}

private static void handleAccept(SelectionKey key, Selector selector) throws IOException {

// 获取 ServerSocketChannel

ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

// 接受新的客户端连接

SocketChannel clientSocketChannel = serverSocketChannel.accept();

// 设置客户端通道为非阻塞模式

clientSocketChannel.configureBlocking(false);

// 注册 OP_READ 事件,表示关注该通道的可读事件

clientSocketChannel.register(selector, SelectionKey.OP_READ);

System.out.println("New client connected: " + clientSocketChannel.getRemoteAddress());

}

private static void handleRead(SelectionKey key) throws IOException {

// 获取 SocketChannel

SocketChannel clientSocketChannel = (SocketChannel) key.channel();

// 创建直接缓冲区

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

// 从通道读取数据到缓冲区

int bytesRead = clientSocketChannel.read(buffer);

if (bytesRead == -1) {

// 如果没有读取到数据,关闭连接

clientSocketChannel.close();

return;

}

// 切换缓冲区模式为读模式

buffer.flip();

byte[] bytes = newbyte[buffer.remaining()];

buffer.get(bytes);

String message = new String(bytes, "UTF-8");

System.out.println("Received from client: " + message);

// 回显消息给客户端

ByteBuffer responseBuffer = ByteBuffer.allocateDirect(("Echo: " + message).getBytes("UTF-8").length);

responseBuffer.put(("Echo: " + message).getBytes("UTF-8"));

responseBuffer.flip();

clientSocketChannel.write(responseBuffer);

}

}

客户端代码:

package com.example.nio;

import java.io.*;

import java.net.Socket;

publicclass NIOClient {

public static void main(String[] args) throws IOException {

// 连接到服务器的 8080 端口

Socket socket = new Socket("localhost", 8080);

System.out.println("Connected to server");

// 创建标准输入流读取用户输入

BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));

// 创建输入流读取服务器发送的数据

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

// 创建输出流向服务器发送数据

PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

String userInputLine;

// 循环读取用户输入并发送到服务器

while ((userInputLine = userInput.readLine()) != null) {

out.println(userInputLine);

// 读取服务器的响应并打印

System.out.println("Server response: " + in.readLine());

}

// 关闭连接

socket.close();

}

}

性能对比

线程数量:

BIO: 每个连接一个线程,线程数量与连接数量成正比。高并发时,线程数量急剧增加,可能导致资源耗尽。

NIO: 单个线程管理多个连接,线程数量固定,不受连接数量影响。即便在高并发情况下,也能保持较低的线程数量,节省资源。

资源消耗:

BIO: 线程切换和上下文切换开销大,资源消耗高。每个线程都需要占用一定的内存和 CPU 时间片,尤其是在高并发场景下。

NIO: 资源消耗较低,适合高并发场景。由于使用单个线程管理多个连接,减少了线程切换的开销,提高了资源利用率。

吞吐量:​​​​​​​

BIO: 吞吐量受限于线程数量,高并发时性能下降。每个线程只能处理一个连接,随着连接数的增加,吞吐量迅速下降。

NIO: 吞吐量较高,能够处理更多的并发连接。通过多路复用技术,单个线程可以高效地处理多个连接,提高了系统的吞吐量。

数据处理效率:​​​​​​​

BIO: 数据以字节流形式逐字节或逐块传输,缺乏缓冲机制。频繁的系统调用导致性能下降。

NIO: 数据存储在缓冲区中,可以通过一次系统调用来批量读取或写入数据。减少了系统调用次数,提高了 I/O 效率。

直接缓冲区:​​​​​​​

BIO: 数据需要在 JVM 堆内存和操作系统内核空间之间多次复制。增加了额外的数据复制开销,降低了 I/O 性能。

NIO: 直接缓冲区直接在 JVM 堆外内存中分配,避免了数据在 JVM 堆内存和操作系统内核空间之间的复制。减少了数据复制的开销,提高了 I/O 操作的速度。

总结

通过上述代码示例和分析,可以看出 Java NIO 相较于 Java BIO 在以下几个方面具有显著的优势:

非阻塞 I/O:减少了线程阻塞的时间,提高了资源利用率。

多路复用:通过 Selector 可以高效地管理多个连接,降低了线程的数量。

缓冲区:批量处理数据减少了系统调用次数,提高了 I/O 效率。

直接缓冲区:减少数据复制的开销,提高了 I/O 操作的速度。

通道:双向通信简化了数据处理流程,减少了代码复杂度。

异步 I/O(NIO.2):进一步提升了系统的吞吐量和响应速度。

这些特性使得 NIO 成为处理高并发、大规模 I/O 操作的理想选择,尤其适用于现代互联网应用和服务端开发。

/**

* 这段代码只有Java开发者才能看得懂!

* 关注我微信公众号之后,

* 发送:"666",

* 即可获得一本由Java大神一手面试经验诚意出品

* 《Java开发者面试百宝书》Pdf电子书

* 福利截止日期为2025年01月22日止

* 手快有手慢没!!!

*/

System.out.println("请关注我的微信公众号:");

System.out.println("Java知识日历");

关注我!Java从此不迷路!

相关推荐

Carbon Soldier
www.bst365.com

Carbon Soldier

📅 09-23 👀 9425
男生为什么怕女生(男生为何害怕女生)
365bet盘口

男生为什么怕女生(男生为何害怕女生)

📅 09-18 👀 4684
袋鼩的自杀式繁殖:一生只繁殖一次,然后就死了
【肾世图卷】生物钟和肾脏
365bet盘口

【肾世图卷】生物钟和肾脏

📅 07-11 👀 1192