面试题答案
一键面试运用Java NIO改进BIO阻塞问题的方法
-
NIO核心组件的作用
- Channel(通道):在NIO中,Channel是双向的,可以同时进行读和写操作,与BIO中的Stream单向不同。它就像是一个连接到数据源头或目标的管道,数据通过Channel进行传输。例如,
SocketChannel
用于TCP网络通信,FileChannel
用于文件的读写操作。 - Buffer(缓冲区):是NIO中数据的载体,所有数据都要先读到一个Buffer中,或者从一个Buffer中写入到Channel。Buffer本质上是一块内存区域,提供了对数据的结构化访问以及维护读写位置等状态信息。常见的Buffer类型有
ByteBuffer
、CharBuffer
等。 - Selector(选择器):是NIO实现多路复用的关键组件。它允许一个线程管理多个Channel,通过Selector,线程可以不断地轮询注册在其上的Channel,一旦某个Channel有数据可读或者可写,Selector就会感知到并通知对应的线程进行处理,这样可以大大提高程序的并发处理能力。
- Channel(通道):在NIO中,Channel是双向的,可以同时进行读和写操作,与BIO中的Stream单向不同。它就像是一个连接到数据源头或目标的管道,数据通过Channel进行传输。例如,
-
关键代码实现思路示例
- 服务器端示例:
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 {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public NIOServer(int port) throws IOException {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void listen() throws IOException {
while (true) {
int readyChannels = selector.select();
if (readyChannels == 0) continue;
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 socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = socketChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("Received: " + new String(data));
}
}
keyIterator.remove();
}
}
}
public static void main(String[] args) {
try {
NIOServer server = new NIOServer(8080);
server.listen();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- **代码解释**:
- 首先创建一个`Selector`和`ServerSocketChannel`,并将`ServerSocketChannel`设置为非阻塞模式,然后注册到`Selector`上监听`OP_ACCEPT`事件。
- 在`listen`方法的循环中,通过`selector.select()`阻塞等待有事件发生。当有事件发生时,获取`selectedKeys`,遍历处理每个`SelectionKey`。
- 如果是`OP_ACCEPT`事件,表示有新的客户端连接,接受连接并将新的`SocketChannel`设置为非阻塞模式,然后注册到`Selector`上监听`OP_READ`事件。
- 如果是`OP_READ`事件,表示客户端有数据可读,读取数据并处理。
- **客户端示例**:
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));
ByteBuffer buffer = ByteBuffer.wrap("Hello, NIO Server!".getBytes());
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- **代码解释**:
- 创建`SocketChannel`并连接到服务器,将数据包装到`ByteBuffer`中,然后通过`SocketChannel`将数据发送到服务器。