JavaIO与NIO
Java I/O 分经典 IO(java.io,阻塞流式)和 NIO(java.nio,非阻塞通道式)两代,核心差异是"阻塞 vs 非阻塞"和"流 vs 通道"。经典 IO 每个连接一个线程,连接多时线程爆炸;NIO 用选择器(Selector)单线程管理海量连接,是 Netty 等高性能网络框架的底层。选型口诀:简单文件读写用经典 IO,高并发网络编程用 NIO。
经典 IO 流体系
java.io 的核心是 InputStream/OutputStream(字节流)和 Reader/Writer(字符流)两对抽象,组合装饰器模式扩展功能:
| 场景 | 选择 |
|---|---|
| 读二进制文件 | FileInputStream + BufferedInputStream |
| 读文本文件 | BufferedReader + FileReader(指定编码) |
| 写文本文件 | BufferedWriter + FileWriter(指定编码) |
| 对象持久化 | ObjectOutputStream/ObjectInputStream(需实现 Serializable,见下方序列化章节) |
// 逐行读文本(try-with-resources 自动关闭)
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(
new FileInputStream("a.txt"), StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}编码陷阱
字符流默认平台编码。中文环境必须显式指定 UTF-8,否则跨平台部署时出现乱码。Java 18+ 已默认 UTF-8,旧版本需留意。
对象序列化
Java 序列化把对象转成字节流,用于持久化和网络传输,核心是实现 Serializable 接口:
public class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号,必须显式声明
private String name;
private transient int password; // transient 字段不序列化
}| 问题 | 解决 |
|---|---|
| 类结构变化后反序列化失败 | 保持 serialVersionUID 一致 |
| 敏感字段泄露 | 使用 transient |
| 单例被序列化破坏 | 添加 readResolve() |
现代实践
JSON 序列化(Jackson/Gson)已取代大部分 Java 原生序列化场景——更安全(无反序列化漏洞)、跨语言、性能更好。原生序列化主要用于分布式 RPC 的二进制协议(如 Dubbo 的 Hessian)。
BIO 阻塞模型
BIO(Blocking IO)是经典 IO 的同步阻塞模型:线程发起读/写后一直阻塞,直到数据就绪。每个连接需要专属线程:
- 优点:模型简单、易实现
- 缺点:一连接一线程,线程开销大;大量空闲连接时线程全在阻塞空等
NIO 非阻塞模型
NIO 的三个核心组件:Channel(通道,双向)、Buffer(缓冲区)、Selector(选择器,多路复用)。非阻塞 + 多路复用的组合,让一个线程管理成千上万个连接:
| 特性 | BIO | NIO |
|---|---|---|
| 阻塞方式 | 阻塞 | 非阻塞 |
| 数据单位 | 流(Stream) | 缓冲区(Buffer) |
| 方向 | 单向 | 通道双向 |
| 连接管理 | 一连接一线程 | 一选择器多连接 |
| 适用 | 连接少、数据量大 | 连接多、数据量小 |
// NIO 核心使用模式:注册感兴趣的事件,select 返回就绪集合
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.configureBlocking(false); // 必须非阻塞
server.register(selector, SelectionKey.OP_ACCEPT); // 注册接受连接事件
while (true) {
selector.select(); // 阻塞直到有事件
for (SelectionKey key : selector.selectedKeys()) {
if (key.isAcceptable()) { /* 接受新连接 */ }
if (key.isReadable()) { /* 读取数据到 Buffer */ }
}
}什么时候用 NIO
NIO 编程模型复杂(buffer 管理、半包粘包处理),直接手写易出错——Netty 就是对 NIO 的封装,生产环境优先用 Netty。连接数少(几十个内)用 BIO 更简单,连接数上千再上 NIO/Netty。
IO 与 Java异常处理 的 try-with-resources(自动关闭流)、Java字节码 的执行模型、Java线程池 的高并发线程管理密切相关。