Skip to content

IO 高频面试题

面试问到 IO,十有八九会扯到 BIO、NIO、零拷贝、序列化。本节用矩阵对比的方式,把这些问题讲清楚。

BIO vs NIO vs AIO

特性BIONIOAIO
全称Blocking IONew IO / Non-blocking IOAsynchronous IO
IO 类型同步阻塞同步非阻塞异步非阻塞
线程模型1 连接 1 线程1 线程管 N 连接事件驱动
适用场景连接少(< 1000)高并发(> 1000)IO 密集
复杂度
JDK 版本1.01.41.7

BIO:每个连接一个线程

java
// 一个线程处理一个连接
ServerSocket server = new ServerSocket(8080);
while (true) {
    Socket client = server.accept(); // 阻塞
    new Thread(() -> handle(client)).start(); // 为每个连接创建线程
}

问题:10000 连接 = 10000 线程 = 内存爆炸,上下文切换开销巨大。

NIO:多路复用,1 线程管 N 连接

java
// 一个线程管理多个连接
ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(8080));
server.configureBlocking(false); // 非阻塞

Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);

while (selector.select() > 0) {
    for (SelectionKey key : selector.selectedKeys()) {
        if (key.isAcceptable()) {
            // 处理新连接
        } else if (key.isReadable()) {
            // 处理读事件
        } else if (key.isWritable()) {
            // 处理写事件
        }
    }
    selector.selectedKeys().clear();
}

优势:单线程管理大量连接,适合高并发场景。

AIO:异步回调

java
// 操作发起后立即返回,IO 完成时回调
AsynchronousServerSocketChannel server =
    AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));

server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
    @Override
    public void completed(AsynchronousSocketChannel client, Void attachment) {
        server.accept(null, this); // 继续接受下一个
        ByteBuffer buf = ByteBuffer.allocate(1024);
        client.read(buf, null, new CompletionHandler<Integer, Void>() {
            @Override
            public void completed(Integer result, Void attachment) {
                buf.flip();
                // 处理读取的数据
            }
        });
    }
});

NIO 三大核心组件

组件作用比喻
Buffer缓冲区,存储数据水缸
Channel通道,传输数据水管
Selector选择器,监听事件阀门

Buffer:数据的载体

java
ByteBuffer buf = ByteBuffer.allocate(1024);

// 写模式
buf.put("hello".getBytes());

// 切换读模式
buf.flip();

// 读数据
while (buf.hasRemaining()) {
    System.out.print((char) buf.get());
}

// 清空,准备下次写入
buf.clear();

Channel:数据的通道

java
FileChannel in = new FileInputStream("file.txt").getChannel();
FileChannel out = new FileOutputStream("out.txt").getChannel();

ByteBuffer buf = ByteBuffer.allocate(8192);
while (in.read(buf) != -1) {
    buf.flip();
    out.write(buf);
    buf.clear();
}

零拷贝

传统文件传输需要 4 次拷贝:

磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡

零拷贝只需要 2 次拷贝:

磁盘 → 内核缓冲区(sendfile)→ 网卡

Java 实现:

java
FileChannel in = new FileInputStream("bigfile.mp4").getChannel();
FileChannel out = new FileOutputStream("copy.mp4").getChannel();

long size = in.size();
long transferred = 0;
while (transferred < size) {
    transferred += in.transferTo(transferred, size - transferred, out);
}

适用场景:大文件传输,如视频服务、文件服务器。


序列化与反序列化

问题答案
什么可以序列化?实现 Serializable 接口的对象
serialVersionUID 是什么?版本号,反序列化时对比,版本不一致抛异常
transient 关键字的作用?跳过字段,不参与序列化
java
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private transient String password; // 不序列化
    private int age;
}

// 自定义序列化
private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject();
    out.writeObject(encrypt(password));
}

private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    this.password = decrypt((String) in.readObject());
}

Java 序列化 vs JSON

特性Java 序列化JSON
格式二进制文本
体积较大
跨语言
性能较慢

字符编码

为什么会乱码? 编码和解码用了不同的字符集。

"中" → UTF-8 → [0xE4, 0xBD, 0xA0] → 用 GBK 解码 → 乱码!

如何避免?

java
// ❌ 错误:依赖平台默认编码
new FileReader("file.txt");

// ✅ 正确:显式指定 UTF-8
new InputStreamReader(
    new FileInputStream("file.txt"),
    StandardCharsets.UTF_8);

// ✅ JDK 11+
String content = Files.readString(Path.of("file.txt"), StandardCharsets.UTF_8);

资源泄漏

java
// ❌ 没有关闭,抛异常时泄漏
FileInputStream fis = new FileInputStream("file.txt");

// ✅ try-with-resources
try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 自动关闭
}

记住这个面试重点矩阵

问题核心答案
BIO vs NIO vs AIO阻塞 vs 非阻塞 vs 异步
NIO 三件套Buffer、Channel、Selector
零拷贝transferTo() 减少拷贝
序列化serialVersionUID 控制版本,transient 跳过字段
字符编码显式指定 UTF-8
资源泄漏try-with-resources

面试不是背书,理解原理才能举一反三。

基于 VitePress 构建