在 Java 的世界里,I/O(输入/输出)是一个绕不开的话题。无论是文件读写、网络通信,还是与外部系统的交互,都离不开 I/O 操作。Java 的 I/O 体系经历了从传统的 BIO(Blocking I/O)到 NIO(Non-blocking I/O),再到 AIO(Asynchronous I/O)的演进。本文将带你全面了解 Java I/O 的核心概念、使用方式和背后的设计思想。
一、Java I/O 概述
Java 的 I/O 主要分为两大类:字节流 和字符流,分别用于处理二进制数据和文本数据。每个大类又细分为输入流和输出流。
1.1 字节流
-
InputStream:所有字节输入流的父类,抽象了从数据源读取字节的能力。 -
OutputStream:所有字节输出流的父类,抽象了向目的地写入字节的能力。
1.2 字符流
-
Reader:所有字符输入流的父类,用于读取字符数据(自动处理字符编码)。 -
Writer:所有字符输出流的父类,用于写入字符数据。
1.3 装饰器模式
Java I/O 大量运用了装饰器模式,通过包装基础流来添加新功能(如缓冲、压缩、对象序列化等)。例如:
java
// 用缓冲流包装文件流,提高读写效率
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("data.txt"));
这种设计让开发者可以灵活组合,但也带来了类的数量繁多、层次复杂的问题。
二、BIO -- 传统的阻塞 I/O
BIO(Blocking I/O)是 Java 1.4 之前唯一的 I/O 模型。它的特点是:每次 I/O 操作(读或写)都会阻塞当前线程,直到数据就绪或操作完成。
2.1 简单文件读写示例
java
import java.io.*;
public class BioFileDemo {
public static void main(String[] args) {
// 写入文件
try (FileOutputStream fos = new FileOutputStream("example.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
String content = "Hello, Java I/O!";
bos.write(content.getBytes());
System.out.println("写入完成");
} catch (IOException e) {
e.printStackTrace();
}
// 读取文件
try (FileInputStream fis = new FileInputStream("example.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
byte[] buffer = new byte[1024];
int length;
while ((length = bis.read(buffer)) != -1) {
System.out.println(new String(buffer, 0, length));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 BIO 的局限
BIO 模型简单直观,但存在一个致命问题:每个连接都需要一个独立的线程 。在传统的网络编程中,服务端 accept() 后,会为每个客户端创建一个线程处理请求。当并发连接数很高时,系统会创建大量线程,导致线程切换开销大、内存占用高,甚至资源耗尽。
三、NIO -- 非阻塞 I/O
Java 1.4 引入了 NIO(New I/O),提供了非阻塞 和多路复用 的能力。NIO 的核心组件是 Channel 、Buffer 和 Selector。
-
Channel:类似传统流,但可以异步读写,支持双向传输(既可读也可写)。
-
Buffer:数据容器,所有读写操作都通过 Buffer 完成。
-
Selector:单线程管理多个 Channel,通过事件驱动机制(如读就绪、写就绪)实现高并发。
3.1 NIO 文件拷贝示例
java
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class NioFileDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("source.txt");
FileOutputStream fos = new FileOutputStream("dest.txt");
FileChannel readChannel = fis.getChannel();
FileChannel writeChannel = fos.getChannel();
// 直接使用 transferTo 实现零拷贝(操作系统级别)
readChannel.transferTo(0, readChannel.size(), writeChannel);
readChannel.close();
writeChannel.close();
fis.close();
fos.close();
}
}
3.2 基于 Selector 的网络服务器模型(简化)
java
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
public class NioServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress(8080));
serverSocket.configureBlocking(false); // 非阻塞
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
selector.select(); // 阻塞直到有事件就绪
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) {
SocketChannel client = serverSocket.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = client.read(buffer);
if (read == -1) {
client.close();
continue;
}
buffer.flip();
// 处理数据...
}
selector.selectedKeys().clear();
}
}
}
}
NIO 的优势:单个线程可以同时处理成千上万个连接,避免了频繁创建线程的开销。基于事件驱动,资源利用率高。
四、AIO -- 异步 I/O
Java 1.7 引入了 AIO(Asynchronous I/O),也称为 NIO.2。AIO 是真正意义上的异步 I/O:当发起一个 I/O 操作后,线程立即返回,待操作完成后,系统通过回调通知应用程序。
AIO 使用 AsynchronousChannel 接口,如 AsynchronousFileChannel、AsynchronousSocketChannel 等。
4.1 AIO 文件读取示例
java
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.concurrent.*;
public class AioFileDemo {
public static void main(String[] args) throws Exception {
Path file = Paths.get("example.txt");
AsynchronousFileChannel channel = AsynchronousFileChannel.open(file, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 发起异步读取,通过 CompletionHandler 回调
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
System.out.println("读取内容:" + new String(attachment.array(), 0, result));
try { channel.close(); } catch (Exception e) { e.printStackTrace(); }
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("读取失败:" + exc.getMessage());
try { channel.close(); } catch (Exception e) { e.printStackTrace(); }
}
});
// 主线程可以继续做其他事情
Thread.sleep(2000); // 等待回调完成,实际开发中可用 CountDownLatch
}
}
4.2 AIO 网络通信(简单示例)
java
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
public class AioServer {
public static void main(String[] args) throws Exception {
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel client, Void attachment) {
// 继续接收下一个连接
server.accept(null, this);
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
System.out.println("收到数据:" + new String(attachment.array(), 0, result));
try { client.close(); } catch (Exception e) { e.printStackTrace(); }
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Void attachment) {
exc.printStackTrace();
}
});
Thread.sleep(30000); // 保持服务运行
}
}
AIO 的优势:彻底解放线程,I/O 操作完全由操作系统异步完成,适合高并发、大文件等场景。但复杂度较高,目前实际应用中不如 NIO 普及(如 Netty 基于 NIO)。
五、性能对比与适用场景
| I/O 模型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| BIO | 连接数少且固定的场景(如管理后台、简单服务) | 编程简单,易于理解 | 每个连接一个线程,无法支撑高并发 |
| NIO | 高并发网络服务(如聊天服务器、网关) | 单线程管理多连接,资源占用少 | 编程复杂,需要处理事件、缓冲区管理等 |
| AIO | 大文件读写、高并发且 I/O 操作较重的场景 | 完全异步,调用者无需等待 | 编程复杂度高,回调地狱,成熟框架较少 |
六、总结与展望
Java I/O 的演进体现了对性能的不断追求:
-
BIO 简单可靠,适合小规模应用;
-
NIO 通过多路复用实现了高并发,成为现代高性能服务器的基石(如 Netty、Tomcat NIO 模式);
-
AIO 进一步将异步能力交给操作系统,但在实际应用中尚未完全取代 NIO。
对于大多数开发者来说,理解 NIO 的原理已经足够应对日常开发。如果想深入,可以研究 Netty 框架,它封装了 NIO 的复杂性,提供了优雅的 API 和稳定的性能。