在网络编程的世界里,I/O操作是至关重要的组成部分,但传统的阻塞式I/O模型却常常成为性能瓶颈。想象一下,当你在一台服务器上处理数千个并发连接时,每个连接都需要一个独立的线程来处理读写操作。这不仅消耗了大量的系统资源,还导致了严重的上下文切换开销。于是,Java NIO(Non-blocking I/O)应运而生,为我们带来了非阻塞I/O的解决方案,开启了高并发处理的新纪元。
第二部分:非阻塞I/O的实践案例
实例1:基于NIO的简单聊天服务器
我们将构建一个简单的聊天服务器,使用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.HashSet;
import java.util.Set;
import java.util.Iterator;
public class ChatServer {
private static final int PORT = 8080;
private static final ByteBuffer BUFFER_SIZE = ByteBuffer.allocate(1024);
private static final Set<SocketChannel> clients = new HashSet<>();
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(PORT));
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select();
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
acceptNewClient(serverChannel, selector);
}
if (key.isReadable()) {
readFromClient((SocketChannel) key.channel());
}
it.remove();
}
}
}
private static void acceptNewClient(ServerSocketChannel serverChannel, Selector selector) throws IOException {
SocketChannel client = serverChannel.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
clients.add(client);
}
private static void readFromClient(SocketChannel client) throws IOException {
BUFFER_SIZE.clear();
int bytesRead = client.read(BUFFER_SIZE);
if (bytesRead == -1) {
clients.remove(client);
client.close();
} else {
BUFFER_SIZE.flip();
byte[] data = new byte[BUFFER_SIZE.limit()];
BUFFER_SIZE.get(data);
String message = new String(data).trim();
System.out.println("Received: " + message);
broadcastToAll(message, client);
BUFFER_SIZE.clear();
}
}
private static void broadcastToAll(String message, SocketChannel sender) throws IOException {
for (SocketChannel client : clients) {
if (client != sender) {
ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
client.write(buffer);
}
}
}
}
在这个例子中,我们首先创建了一个Selector
并打开了一个ServerSocketChannel
,将其绑定到一个端口上。然后,我们在一个无限循环中使用Selector
来监听新连接和已连接客户端的可读事件。当有新的客户端连接时,我们接受这个连接并将SocketChannel
注册到Selector
上,设置其为非阻塞模式。当SocketChannel
上有可读事件时,我们读取数据并广播给所有其他在线的客户端。
实例2:文件复制
下面是一个使用NIO进行文件复制的例子,展示如何使用FileChannel
和ByteBuffer
进行高效的数据传输。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.channels.FileChannel;
public class FileCopier {
public static void main(String[] args) {
String sourcePath = "source.txt";
String destPath = "destination.txt";
try (FileChannel inChannel = FileChannel.open(Paths.get(sourcePath), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(destPath), StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW,
StandardOpenOption.TRUNCATE_EXISTING)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inChannel.read(buffer) > 0) {
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,我们首先打开源文件和目标文件的FileChannel
,然后使用一个ByteBuffer
作为中介来读取和写入数据。FileChannel
的read()
和write()
方法分别用于填充ByteBuffer
和清空它,从而实现了数据的高效传输。