在Java中,处理I/O操作的模型主要有四种:阻塞I/O (BIO), 非阻塞I/O (NIO), 异步I/O (AIO), 以及IO多路复用。下面详细介绍这四种I/O模型的工作原理和应用场景。
阻塞I/O是最传统的I/O模型。在这种模型中,当一个线程发起一个I/O请求(如读写操作)时,该线程会被阻塞,直到I/O操作完成。这意味着线程必须等待I/O操作完成才能继续执行。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class BioServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8080); System.out.println("Server started on port 8080"); while (true) { Socket clientSocket = serverSocket.accept(); // 阻塞等待客户端连接 new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) { String line; while ((line = reader.readLine()) != null) { System.out.println("Received: " + line); } } catch (IOException e) { e.printStackTrace(); } }).start(); } } } 非阻塞I/O模型允许线程在发起I/O请求时不会被阻塞,如果数据不可用或设备忙,则立即返回一个错误或特殊值。线程可以选择立即再次尝试I/O操作或去做其他事情,从而提高了CPU的利用率。
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 { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); Set selectedKeys = selector.selectedKeys(); Iterator keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int readBytes = sc.read(buffer); if (readBytes > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println("Received: " + new String(data)); } } keyIterator.remove(); } } } } IO多路复用允许一个进程同时监听多个文件描述符(例如socket),并只在某个描述符准备好进行读写操作时才进行处理。常用的多路复用机制有select、poll和epoll。这种模型非常适合处理大量并发连接的场景。
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; public class SelectServer { public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); for (SelectionKey key : selector.selectedKeys()) { if (key.isAcceptable()) { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel sc = ssc.accept(); sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel sc = (SocketChannel) key.channel(); // 读取数据... } } selector.selectedKeys().clear(); } } } select和poll的性能不如epoll,后者仅在Linux系统中可用。异步I/O是真正的异步操作模型,进程发起I/O请求后可以立即返回并继续执行其他任务,而无需等待I/O操作完成。当I/O操作完成后,操作系统会通知进程结果。
import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousServerSocketChannel; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.CountDownLatch; public class AioServer { private static final CountDownLatch latch = new CountDownLatch(1); public static void main(String[] args) throws IOException, InterruptedException { AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open().bind(new java.net.InetSocketAddress(8080)); server.accept(null, new AcceptHandler(server)); latch.await(); } static class AcceptHandler implements CompletionHandler { private AsynchronousServerSocketChannel server; public AcceptHandler(AsynchronousServerSocketChannel server) { this.server = server; } @Override public void completed(AsynchronousSocketChannel result, Object attachment) { result.read(ByteBuffer.allocate(1024), null, new ReadHandler(result)); server.accept(null, this); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); latch.countDown(); } } static class ReadHandler implements CompletionHandler { private AsynchronousSocketChannel channel; public ReadHandler(AsynchronousSocketChannel channel) { this.channel = channel; } @Override public void completed(Integer result, Object attachment) { ByteBuffer buffer = (ByteBuffer) attachment; buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println("Received: " + new String(data)); channel.close(); } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); try { ((AsynchronousSocketChannel) attachment).close(); } catch (IOException e) { e.printStackTrace(); } } } } 选择哪种模型取决于具体的应用场景和需求。例如,对于需要处理大量并发连接的服务器,IO多路复用和异步I/O可能是更佳的选择。而对于简单的、单线程的应用,阻塞I/O可能就已经足够。