IO与NIO
9/6/26JavaJava基础About 4 min
Java IO 体系概览
Java 的 IO 分为传统 BIO(java.io) 与 NIO(java.nio,JDK 4+),后来 NIO.2(JDK 7)又提供了 java.nio.file 与异步通道 AIO。按处理的数据单位可分为字节流与字符流。
- 字节流(InputStream / OutputStream):以
byte为单位,适合所有二进制数据(图片、音视频、压缩包)。 - 字符流(Reader / Writer):以
char(字符)为单位,内部做了字节↔字符的编码转换,适合文本。
字节流 vs 字符流
| 维度 | 字节流 | 字符流 |
|---|---|---|
| 基类 | InputStream / OutputStream | Reader / Writer |
| 单位 | 8 位字节 | 16 位 Unicode 字符 |
| 是否编码 | 不涉及编码,原样读写 | 涉及字符集编解码 |
| 典型用途 | 图片、音频、一切二进制 | 文本文件 |
装饰器模式(Buffered)
Java IO 大量使用装饰器模式,用缓冲流包裹基础流以提升性能(减少系统调用次数)。
// 缓冲字节流复制文件
try (InputStream in = new BufferedInputStream(new FileInputStream("src.jpg"));
OutputStream out = new BufferedOutputStream(new FileOutputStream("dst.jpg"))) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
}
// 缓冲字符流按行读取文本
try (BufferedReader br = new BufferedReader(new FileReader("a.txt", StandardCharsets.UTF_8))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}序列化(Serializable)
对象序列化是把对象转换为字节序列,便于存储或网络传输;反序列化则相反。只需让类实现 java.io.Serializable 标记接口(无方法)。
class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号,建议显式声明
private String name;
private transient int temp; // transient 字段不参与序列化
}
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("u.dat"))) {
oos.writeObject(new User("Tom"));
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("u.dat"))) {
User u = (User) ois.readObject();
}Warning
serialVersionUID建议显式声明,否则类结构变化后反序列化会因 UID 不匹配失败。transient修饰的字段、static字段不会被序列化。- Java 原生序列化体积大、性能差,生产环境多用 JSON(Jackson)或 Protobuf 等。
BIO / NIO / AIO 模型对比
| 模型 | 全称 | 工作方式 | 线程模型 |
|---|---|---|---|
| BIO | Blocking IO | 阻塞式,一个连接一个线程 | 1 连接 : 1 线程 |
| NIO | Non-blocking IO(New IO) | 同步非阻塞,多路复用 | 1 线程管理多连接 |
| AIO | Asynchronous IO | 异步非阻塞,回调通知 | 回调驱动 |
BIO(传统阻塞 IO)
// 每来一个连接就新建一个线程处理,连接多时线程爆炸
ServerSocket server = new ServerSocket(8080);
while (true) {
Socket socket = server.accept(); // 阻塞等待连接
new Thread(() -> handle(socket)).start();
}缺点:并发高时线程数暴涨,上下文切换开销大,伸缩性差。
NIO(多路复用)
NIO 的三大核心:Channel(通道)、Buffer(缓冲区)、Selector(选择器)。通过 Selector 单线程即可监听多个 Channel 的就绪事件。
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 非阻塞
ssc.bind(new InetSocketAddress(8080));
ssc.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()) { /* 接受新连接并注册读事件 */ }
else if (key.isReadable()) { /* 从 Channel 读入 Buffer */ }
it.remove();
}
}NIO 是同步非阻塞:应用线程自己调用 select() 轮询就绪事件(或由操作系统通知就绪后由用户线程处理 IO 读写),读写仍由用户线程完成。Netty、Redis 等高性能网络框架基于此模型。
AIO(异步 IO,NIO.2)
AIO 是异步非阻塞:应用发起 IO 请求后立即返回,真正的读写由操作系统完成后通过回调/ Future 通知。
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open()
.bind(new InetSocketAddress(8080));
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel ch, Void att) {
server.accept(null, this); // 继续接收下一个
// 读取由系统完成,完成后回调
}
@Override
public void failed(Throwable exc, Void att) { exc.printStackTrace(); }
});NIO.2 文件操作(Files / Path)
JDK 7 引入 java.nio.file,日常文件操作用 Files + Path 比传统 File 更简洁强大。
Path path = Paths.get("test.txt");
Files.write(path, "hello".getBytes(StandardCharsets.UTF_8)); // 写
List<String> lines = Files.readAllLines(path); // 读全部行
Files.copy(path, Paths.get("copy.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.walk(Paths.get(".")).forEach(System.out::println); // 遍历目录树
boolean exists = Files.exists(path);选型建议
- 普通文件读写、业务逻辑 → 传统 BIO + 缓冲流,简单直观即可。
- 高并发网络服务器、需要同时处理大量连接 → NIO(或基于 NIO 的 Netty)。
- 追求极致简洁的异步文件/网络 IO,且接受回调风格 → AIO(实际生产中 NIO 生态更成熟)。