Java NIO(New I/O,新 I/O)是 Java 1.4 版本引入的一组用于进行非阻塞 I/O 操作的 API。相比于传统的 Java I/O(或称为 IOStream),Java NIO 提供了更为灵活、可扩展和高性能的 I/O 处理方式。
Java NIO 的核心组件主要包括以下几个部分:
-
通道(Channel)和缓冲区(Buffer):
- 通道代表了连接到实体(如文件、网络套接字等)的打开连接。通道可以进行读取和写入操作。
- 缓冲区是一个对象,它存储了数据,用于在通道之间传输。通常是通过字节缓冲区(ByteBuffer)、字符缓冲区(CharBuffer)、整数缓冲区(IntBuffer)等来操作数据。
-
选择器(Selector):
- 选择器是 Java NIO 实现非阻塞 I/O 的关键部分。它允许单个线程处理多个通道的 I/O 操作。通过选择器,可以实现一个线程管理多个通道的能力,提高了处理连接的效率。
-
通道和选择器的配合使用:
- 通过将通道注册到选择器中,可以让一个选择器监视多个通道上的事件。当一个或多个通道上的事件发生时(比如数据准备就绪、连接就绪等),选择器会通知程序,程序可以处理这些事件。
Java NIO 提供了更底层、更灵活的 API,使得开发者可以更好地控制 I/O 操作,并在处理大量连接时获得更好的性能。它通常用于构建需要高性能网络通信的应用程序,如网络服务器、实时通信系统等。
虽然 Java NIO 提供了更多的控制权和性能优势,但它也更为复杂,相较于传统的 Java I/O,使用起来可能需要更多的编程经验和理解。近年来,一些高级框架(如 Netty、Vert.x 等)基于 Java NIO,使得开发者更容易使用和管理非阻塞 I/O,减少了开发的复杂性。
1.为什么会出现NIO
Java NIO(New I/O)的出现是为了解决传统 I/O 在处理大量连接时的性能问题。在传统的阻塞 I/O 模型中,每个连接都需要一个单独的线程来处理,当连接数量增多时,会导致线程数量暴增,消耗大量系统资源,且效率低下。
Java NIO 的出现主要是为了解决传统 I/O 的几个问题:
-
连接数量与线程开销问题:传统 I/O 模型中,每个连接需要一个独立的线程来处理,线程开销较大。当连接数目增多时,会消耗过多的系统资源,同时线程切换开销也会增加。
-
阻塞与非阻塞:传统 I/O 操作是阻塞的,即当数据准备好之前,线程会一直等待数据的到来。在某些场景下,这种等待会导致资源浪费。
-
事件驱动:在处理网络操作时,传统 I/O 模型中常常需要轮询来检查连接是否有数据可读写,效率较低。
Java NIO 提供了非阻塞的 I/O 操作,通过 Channel 和 Selector 的概念,使得单个线程可以处理多个连接的 I/O 操作,减少了线程数量和系统资源的消耗。它采用了事件驱动的模型,通过选择器(Selector)监听多个通道的事件,并在事件发生时进行相应的处理,避免了不必要的轮询。
这些特性使得 Java NIO 更适用于处理大量连接、高并发的网络应用场景,提高了系统的效率和可伸缩性。虽然 Java NIO 相对于传统 I/O 更为复杂,但它带来了更高的性能和更好的资源利用率,因此得到了广泛的应用。
2.Talk is cheap.Show me your code.
2.1.传统代码实现传统阻塞I/O
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleEchoServer {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("Server started...");
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected: " + clientSocket);
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);
String inputLine;
while ((inputLine = reader.readLine()) != null) {
System.out.println("Received message from client: " + inputLine);
writer.println("Server echo: " + inputLine);
}
reader.close();
writer.close();
clientSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class SimpleEchoClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 8080);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
String userInput;
while ((userInput = userInputReader.readLine()) != null) {
writer.println(userInput);
String response = reader.readLine();
System.out.println("Server response: " + response);
}
writer.close();
reader.close();
userInputReader.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在这个简单的示例中,服务器接受客户端连接,读取客户端发送的消息并将其发送回客户端。客户端连接到服务器并发送消息,然后等待服务器返回的消息并打印。
请注意,这只是一个基于阻塞 I/O 的简单示例,实际应用中可能需要更多的错误处理、线程管理和优化。这种实现方式会为每个连接创建一个新的线程,当连接数增加时可能会导致线程资源的消耗过多。在高并发情况下,这种实现方式可能会遇到性能瓶颈。对于需要更高性能和可伸缩性的网络应用,可以考虑使用基于 Java NIO 的技术框架来实现。
2.2.NIO实现
下面是一个简单的基于 Java NIO 实现的简单通信示例,包括服务端和客户端:
NIO 服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) {
try {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started...");
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("Client connected: " + clientChannel.getRemoteAddress());
} else if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
clientChannel.close();
key.cancel();
System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());
continue;
}
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes).trim();
System.out.println("Received from client " + clientChannel.getRemoteAddress() + ": " + message);
// Echo back to the client
clientChannel.write(ByteBuffer.wrap(("Echo: " + message).getBytes()));
}
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO 客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
String message = "Hello, server!";
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
socketChannel.write(buffer);
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println("Server response: " + new String(bytes).trim());
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个示例是一个简单的基于 Java NIO 的通信示例,包括一个非阻塞的 Echo Server 和一个客户端。服务端接受客户端连接并返回相同的消息。客户端连接到服务器并发送消息,然后等待服务器返回的消息。
在服务器端,通过 ServerSocketChannel
和 SocketChannel
实现了非阻塞的通信,使用 Selector
监听多个通道的事件。在客户端,使用 SocketChannel
连接服务器,并进行数据的读写操作。