知识模块
☕ Java 知识模块
五、Java IO/NIO
IO 性能优化

IO 性能优化

面试提问

"Java IO 性能优化的常见手段有哪些?"


IO 性能瓶颈

瓶颈原因优化方向
磁盘 IO物理寻道慢顺序读写、缓冲
网络 IO网络延迟批量传输、压缩
上下文切换系统调用多零拷贝、NIO
内存拷贝数据多次复制直接内存、零拷贝

缓冲优化

使用缓冲流

// 不推荐:无缓冲
FileInputStream fis = new FileInputStream("file.txt");
int b;
while ((b = fis.read()) != -1) {  // 每次读 1 字节,大量系统调用
    // 处理
}
 
// 推荐:使用缓冲流
BufferedInputStream bis = new BufferedInputStream(
    new FileInputStream("file.txt"));
int b;
while ((b = bis.read()) != -1) {  // 批量读取,减少系统调用
    // 处理
}

自定义缓冲大小

// 默认缓冲区大小:8KB
BufferedInputStream bis = new BufferedInputStream(
    new FileInputStream("file.txt"), 64 * 1024);  // 64KB 缓冲
 
// 自定义缓冲读取
byte[] buffer = new byte[64 * 1024];
int len;
while ((len = fis.read(buffer)) != -1) {
    // 处理 buffer
}

缓冲大小建议

场景建议大小
小文件8KB - 16KB
大文件64KB - 256KB
网络传输4KB - 16KB
SSD 磁盘64KB - 1MB

直接内存(DirectBuffer)

堆内存 vs 直接内存

// 堆内存 Buffer
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);
 
// 直接内存 Buffer(零拷贝友好)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

性能对比

对比项堆内存直接内存
分配速度
IO 性能低(需复制)高(零拷贝)
GC 影响受 GC 管理不受 GC 直接管理
适用场景短生命周期长生命周期、频繁 IO

使用建议

// 频繁 IO 操作使用直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(64 * 1024);
 
// 短生命周期使用堆内存
ByteBuffer buffer = ByteBuffer.allocate(1024);

NIO 多路复用

BIO vs NIO 并发能力

// BIO:每个连接一个线程
while (true) {
    Socket socket = serverSocket.accept();
    new Thread(() -> handle(socket)).start();  // 线程资源有限
}
 
// NIO:一个线程管理多个连接
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
 
while (true) {
    selector.select();
    // 处理就绪的 Channel
}

连接数对比

模型连接数上限资源消耗
BIO~1000高(每连接一线程)
NIO10000+低(线程池 + Selector)

零拷贝

传统 IO vs 零拷贝

// 传统 IO:4 次拷贝,4 次上下文切换
byte[] data = Files.readAllBytes(Paths.get("file.txt"));
socket.getOutputStream().write(data);
 
// 零拷贝:2 次 DMA 拷贝,2 次上下文切换
FileChannel fileChannel = new FileInputStream("file.txt").getChannel();
fileChannel.transferTo(0, fileChannel.size(), socketChannel);

适用场景

  • 文件传输服务
  • 消息队列(Kafka、RocketMQ)
  • 静态资源服务

批量操作

批量读取

// 不推荐:逐行读取处理
List<String> lines = Files.readAllLines(path);  // 一次性加载到内存
for (String line : lines) {
    process(line);
}
 
// 推荐:流式处理
try (Stream<String> stream = Files.lines(path)) {
    stream.forEach(this::process);  // 逐行处理,内存友好
}

批量写入

// 不推荐:多次写入
for (String line : lines) {
    writer.write(line);  // 多次系统调用
}
 
// 推荐:批量写入
StringBuilder sb = new StringBuilder();
for (String line : lines) {
    sb.append(line).append("\n");
}
writer.write(sb.toString());  // 一次写入

异步 IO

异步文件读写

// 异步读取
AsynchronousFileChannel channel = AsynchronousFileChannel.open(
    Paths.get("file.txt"), StandardOpenOption.READ);
 
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = channel.read(buffer, 0);
 
// 做其他事情...
 
Integer bytesRead = future.get();  // 阻塞等待完成
 
// 回调方式
channel.read(buffer, 0, null, new CompletionHandler<Integer, Void>() {
    @Override
    public void completed(Integer result, Void attachment) {
        // 读取完成
    }
    
    @Override
    public void failed(Throwable exc, Void attachment) {
        // 处理异常
    }
});

异步 Socket

AsynchronousServerSocketChannel serverChannel = 
    AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
 
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
    @Override
    public void completed(AsynchronousSocketChannel client, Void attachment) {
        serverChannel.accept(null, this);  // 继续接受连接
        // 处理客户端
    }
    
    @Override
    public void failed(Throwable exc, Void attachment) {
        // 处理异常
    }
});

文件系统优化

顺序读写 vs 随机读写

// 随机读写:性能差
RandomAccessFile raf = new RandomAccessFile("file.txt", "rw");
raf.seek(100);  // 定位
raf.write(data);
raf.seek(500);  // 再定位
raf.write(data2);
 
// 顺序读写:性能好
try (FileChannel channel = new FileOutputStream("file.txt").getChannel()) {
    for (byte[] data : dataList) {
        channel.write(ByteBuffer.wrap(data));  // 顺序写入
    }
}

内存映射文件

// 大文件处理
FileChannel channel = new RandomAccessFile("large.txt", "r").getChannel();
MappedByteBuffer buffer = channel.map(
    FileChannel.MapMode.READ_ONLY, 
    0, 
    Math.min(channel.size(), Integer.MAX_VALUE));
 
// 直接从内存读取
while (buffer.hasRemaining()) {
    byte b = buffer.get();
    // 处理
}

网络传输优化

TCP 参数优化

ServerSocketChannel serverChannel = ServerSocketChannel.open();
ServerSocket socket = serverChannel.socket();
 
socket.setReuseAddress(true);        // 允许端口重用
socket.setReceiveBufferSize(64 * 1024);  // 接收缓冲区大小
 
SocketChannel clientChannel = serverChannel.accept();
Socket clientSocket = clientChannel.socket();
 
clientSocket.setTcpNoDelay(true);    // 禁用 Nagle 算法
clientSocket.setKeepAlive(true);     // 保持连接
clientSocket.setSendBufferSize(64 * 1024);  // 发送缓冲区大小

数据压缩

// 压缩传输
try (GZIPOutputStream gzip = new GZIPOutputStream(
        socket.getOutputStream())) {
    gzip.write(data);
}
 
// 解压接收
try (GZIPInputStream gzip = new GZIPInputStream(
        socket.getInputStream())) {
    byte[] buffer = new byte[1024];
    int len;
    while ((len = gzip.read(buffer)) != -1) {
        // 处理
    }
}

性能监控

关键指标

指标说明工具
IOPS每秒 IO 次数iostat
吞吐量每秒数据量iostat
延迟IO 响应时间iostat
队列深度等待的 IO 请求数iostat

Java 监控

// 文件监控
FileChannel channel = FileChannel.open(path);
long position = channel.position();
long size = channel.size();
 
// 网络监控
Socket socket = channel.socket();
int sendBuffer = socket.getSendBufferSize();
int receiveBuffer = socket.getReceiveBufferSize();

面试要点总结

问题答案要点
如何优化 IO 性能?缓冲、NIO、零拷贝、直接内存
缓冲大小怎么选?小文件 8-16KB,大文件 64-256KB
什么时候用直接内存?频繁 IO、长生命周期对象
零拷贝优势?减少 CPU 拷贝和上下文切换
顺序 vs 随机读写?顺序读写性能更好

参考资料