1156 字
约 3 分钟
4
S04-J06 · IO 与 NIO:四种 IO 模型、Buffer/Channel 与网络编程

适用读者:写文件读写、或要理解 Netty/Reactor 的人。
前置:S04-B07、S02-B04(TCP 可靠传输)。
覆盖旧文:A-1687(198) A-1688(108) A-1684(128) A-1680(148) A-1682(145) A-1706(177) A-1709(89) A-1707(108) A-1708(188) A-1725(99) A-1723(159) A-2332(227) = 1,876 点;A-632(OkHttp)→ S06 duplicate_ref。
覆盖扩展:E-S04-018(四种 IO 模型)。
上一篇:S04-J05 · SPI 与运行时可扩展系统设计
下一篇:S04-A01 · Java 版本演进与团队基线。


1. 一句话结论

IO 的本质是"数据在用户态缓冲区和内核缓冲区之间搬运"。四种模型的差别,只在"谁等、等多久、一次等几个"。

模型 谁在等 一次等几个连接
阻塞 IO(BIO) 线程 1
非阻塞 IO(NIO) 线程轮询 1(但可循环)
IO 多路复用(epoll/select) 内核帮忙等,线程休眠 N 个
异步 IO(AIO) 内核完成后再通知 N 个

"Java 的 NIO"其实是"IO 多路复用 + Buffer/Channel 抽象",不是"异步 IO"。这个命名容易误导。


2. 传统 IO:流(Stream)与装饰器

try (BufferedReader r = new BufferedReader(new FileReader(path))) {
    String line;
    while ((line = r.readLine()) != null) { /* ... */ }
}
类族 单位 典型
字节流 byte InputStream/OutputStream、FileInputStream
字符流 char Reader/Writer、FileReader
缓冲流 装饰器 BufferedInputStream(减少系统调用)

装饰器模式的价值:new BufferedReader(new InputStreamReader(in, UTF_8)) —— 缓冲、编码、字节流三者自由组合。"缓冲"是最值得加的一层(一次读 8KB vs 每次读 1 字节)。


3. NIO 三件套:Channel / Buffer / Selector

组件 作用 与传统 IO 的差别
Channel 双向通道 流是单向的
Buffer 数据容器(position/limit/capacity) 数据先读到 Buffer 再处理
Selector 多路复用器 一个线程管多个 Channel

Buffer 的三个指针(最容易混):

flip()  写模式 → 读模式(limit = position, position = 0)
clear() 读模式 → 写模式(position = 0, limit = capacity)
compact() 保留未读数据,准备继续写

判据:"忘了 flip()"是 NIO 的第一大 bug——写完直接读,读到的是 0 字节。


4. 四种 IO 模型与 Java 的对应(E-S04-018)

模型 Linux 机制 Java 对应
阻塞 read 阻塞 InputStream.read()
非阻塞 O_NONBLOCK channel.configureBlocking(false)
多路复用 select/poll/epoll Selector(底层 epoll)
异步 io_uring/AIO AsynchronousFileChannel(平台支持有限)

为什么 NIO 能撑高并发:一个线程 + 一个 Selector 可以管理上万个连接,因为"等待"由内核完成,线程只在"有事件"时被唤醒(避免"每连接一线程"的栈内存与切换成本,S02-B02)。


5. 网络编程:从 Socket 到 HTTP 服务器(A-1725/A-1723)

层次 用到的类
Socket(阻塞) ServerSocket + Socket
NIO ServerSocketChannel + Selector
HTTP 解析 手写解析请求行/头/体(教学用)

实测级判据:"手写 HTTP 服务器"是理解协议最快的方式——你会立刻明白 B05 讲的"消息边界"为什么必须由协议定义(Content-Length/chunked)。


6. 实践建议

场景 选择
普通文件读写 传统 IO + Buffered*(简单够用)
大文件拷贝 FileChannel.transferTo(零拷贝,不走用户态)
高并发网络 NIO + Selector(或直接用 Netty)
业务代码 用成熟框架(Netty/Spring WebFlux),别自己写 Selector 循环

7. 五个误区

误区 真相
"NIO 就是异步 IO" 是"New IO"(多路复用 + Buffer)
"NIO 一定更快" 小数据量下传统 IO 更快(Buffer 分配成本)
"Buffer 写完就能读" 必须 flip()
"加缓冲没意义" 显著减少系统调用次数
"自己写 Selector 循环更可控" 边界情况极多,优先用 Netty

8. 自检

  1. 四种 IO 模型的"谁在等、等几个"分别是什么?
  2. 为什么说 Java NIO 不是异步 IO?
  3. Buffer 的 flip() 做了什么?不加会怎样?
  4. 为什么"每连接一个线程"撑不了高并发?
  5. 大文件拷贝为什么用 transferTo?

9. 下一篇

S04-A01 · Java 版本演进与团队基线

进阶层至此结束。下一篇进入架构师层:8→11→17→21 升不升、升了得到什么、风险在哪。


本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1687/1688/1684/1680/1682/1706/1709/1707/1708/1725/1723/2332(A-632 → S06);扩展点 E-S04-018。

S04-J06 · IO 与 NIO:四种 IO 模型、Buffer/Channel 与网络编程
https://www.clxhxhhr.top/posts/5229/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。