Java IO面试合集
IO流(BIO)
根据数据传输方式,可以将 IO 类分为:
字节流:字节流读取单个字节,字节流用来处理二进制文件(图片、MP3、视频文件)。
字符流:字符流读取单个字符(一个字符根据编码的不同,对应的字节也不同,如 UTF-8 编码中文汉字是 3 个字节,GBK编码中文汉字是 2 个字节。),字符流用来处理文本文件(可以看做是特殊的二进制文件,使用了某种编码,人可以阅读)。

根据数据操作对象,可以将 IO 类分为:
设计模式
装饰者模式(Decorator):把装饰者套在被装饰者之上,从而动态扩展被装饰者的功能,装饰器模式通过组合替代继承来扩展原始类的功能。即可以在不改变原有对象的情况下拓展其功能。
1 | BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName)); |
操作系统IO模型
异步IO
| Linux | MacOS | Window | |
|---|---|---|---|
| 事件驱动IO | select poll epoll |
kqueue |
无 |
| 异步IO | POSIXio_uring (kernel5.1+) |
无 | IOCP |
事件驱动IO / IO多路复用
| IO模型 | 相对性能 | 关键思路 | 操作系统 | JAVA支持情况 |
|---|---|---|---|---|
select |
较高 | Reactor | windows/Linux | 支持,Reactor模式(反应器设计模式)。Linux操作系统的 kernels 2.4内核版本之前,默认使用select;而目前windows下对同步IO的支持,都是select模型 |
poll |
较高 | Reactor | Linux | Linux下的JAVA NIO框架,Linux kernels 2.6内核版本之前使用poll进行支持。也是使用的Reactor模式 |
epoll |
高 | Reactor/Proactor | Linux | Linux kernels 2.6内核版本及以后使用epoll进行支持;Linux kernels 2.6内核版本之前使用poll进行支持;另外一定注意,由于Linux下没有Windows下的IOCP技术提供真正的 异步IO 支持,所以Linux下使用epoll模拟异步IO |
kqueue |
高 | Proactor | FreeBSD/MacOS | 目前JAVA的版本不支持 |
Java IO模型
BIO (Blocking I/O)
BIO 属于同步阻塞 IO 模型,同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
NIO (Non-blocking/New I/O)
Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO。
Java 异步 I/O 机制:
- NIO(java.nio):
- 引入了非阻塞 I/O 和选择器(Selector),基于操作系统的多路复用机制(如 Linux 的
selectpollepoll)。 - 通过 Selector 和 Channel(如
SocketChannelServerSocketChannelDatagramChannel),Java 可以实现高效的网络 I/O。 - 但这是 就绪通知(readiness-based)模型,而不是真正的完成型异步 I/O。
- 引入了非阻塞 I/O 和选择器(Selector),基于操作系统的多路复用机制(如 Linux 的
- NIO.2(Asynchronous I/O,Java 7+):
- 在 java.nio.channels 包中提供了异步通道类,如
AsynchronousSocketChannelAsynchronousFileChannel - 支持通过 Future 或回调(CompletionHandler)处理 I/O 操作的结果。
- 底层实现依赖操作系统的异步 I/O 接口:
- 在 Linux 上,
AsynchronousFileChannel使用POSIX AIO作为其底层实现机制。AsynchronousSocketChannel使用epoll - 在 Windows 上,使用 IOCP(I/O Completion Ports)。
- 在 macOS 上,使用线程池或 kqueue。
- 在 Linux 上,
- 在 java.nio.channels 包中提供了异步通道类,如
Java 原生提供的NIO过于底层,编写复杂度很高,不推荐程序员直接使用,可以通过Netty框架来进行网络编程。
AIO (Asynchronous I/O)
Java
在Window上使用IOCP,是完全异步的模型。
在Linux和BSD中
AsynchronousFileChannel使用POSIX AIO作为其底层实现机制是完全异步的操作,但AsynchronousSocketChannel使用epoll加线程池模拟异步行为。
目前Java还没有支持Linux的 io_uring 完全异步IO模型。项目中直接使用AIO的情景很少,主要还是使用NIO,如Netty。
总结:目前主流的Socket IO基本上使用NIO(事件驱动IO),需要自行编写网络IO可使用Netty,需要使用Spring就使用Spring WebFlux(对Netty封装)
相同点
在 “提交线程 + 回调函数 + 处理线程”模式下,epoll 和 io_uring 的代码结构几乎一样:
- 提交线程
- 分配缓冲区
buf - 构造上下文
ctx,包含buf和回调函数指针 - 提交任务给内核:
- epoll:
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) - io_uring:
io_uring_prep_read/write(...)+user_data
- epoll:
- 分配缓冲区
- 处理线程
- 阻塞或轮询内核事件:
- epoll:
epoll_wait(epfd, events, ...) - io_uring:
io_uring_wait_cqe(&ring, &cqe)
- epoll:
- 拿到上下文(user_data 或 event.data.ptr)
- 调用回调函数
ctx->cb(ctx, res)
- 阻塞或轮询内核事件:
- 回调函数
- 执行业务逻辑,访问
ctx->buf
- 执行业务逻辑,访问
核心思想完全一样:提交线程只负责发起请求和绑定回调,处理线程只负责事件分发并执行回调
唯一区别
epoll:
内核只告诉 fd 就绪,不完成 I/O
处理线程必须自己
read()/write()代码示例:
1
2ssize_t n = read(ctx->fd, ctx->buf, BUF_SIZE);
ctx->cb(ctx, n);
io_uring:
内核直接完成 I/O,把数据写入 buf
处理线程只需调回调即可,无需再读数据
代码示例:
1
ctx->cb(ctx, cqe->res);
3. 总结
| 特性 | epoll | io_uring |
|---|---|---|
| 提交线程 | 注册 fd + 绑定回调 | 提交具体 I/O + 绑定回调 |
| 处理线程 | 拿到事件,自己 read/write + 调回调 | 拿到完成事件,直接调回调 |
| 回调函数 | 执行业务逻辑 | 执行业务逻辑(buf 已有数据) |
| I/O 异步性 | 半异步(事件就绪通知) | 真正异步(内核完成 I/O) |
✅ 除了 epoll 需要自己在处理线程里 read/write,其他模式和 io_uring 的编程模式几乎一模一样。