Java NIO(New I/O)是Java 1.4引入的高性能I/O框架,通过通道(Channels)、缓冲区(Buffers)和选择器(Selectors)三大核心组件实现了非阻塞I/O操作,使单线程能高效管理多个连接;后续NIO.2(Java 7)进一步增强了文件系统API并引入了真正的异步I/O能力。相比传统IO,NIO在高并发网络服务器(如即时通讯、游戏服务器)、大数据文件处理、实时系统等场景具有显著性能优势,但使用复杂度较高,因此在实际应用中常通过Netty、Vert.x等高级框架间接利用NIO的能力,在保持高性能的同时简化开发。正确使用NIO需要关注资源管理、缓冲区优化和错误处理等最佳实践,以充分发挥其在现代高性能Java应用中的价值。

1. 概述

Java NIO(New I/O)是Java 1.4引入的I/O API,作为传统IO包(java.io)的补充。它提供了更高效的I/O操作,尤其是对高并发场景支持更好。Java 7引入的NIO.2进一步扩展了功能,增加了异步文件和网络操作。

NIO的核心特性

  • 通道(Channels):新型I/O操作的载体
  • 缓冲区(Buffers):数据容器,替代了传统流
  • 选择器(Selectors):实现单线程管理多个通道
  • 非阻塞I/O:允许一个线程处理多个I/O操作
  • 文件操作增强(NIO.2):更强大的文件系统API
  • 异步I/O(NIO.2):真正的非阻塞操作

2. 通道(Channels)

通道是NIO的核心抽象,表示一个开放的连接,可用于读写数据。

2.1 通道类型

  • FileChannel:用于文件读写
  • SocketChannel:TCP客户端通道
  • ServerSocketChannel:TCP服务器通道
  • DatagramChannel:UDP通道
  • AsynchronousSocketChannel:异步TCP通道(NIO.2)
  • AsynchronousServerSocketChannel:异步TCP服务器通道(NIO.2)

2.2 代码示例:文件通道读写

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建文件通道
        RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
        FileChannel channel = file.getChannel();
        
        // 2. 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(48);
        
        // 3. 读取数据到缓冲区
        int bytesRead = channel.read(buffer);
        
        // 4. 处理读取的数据
        while (bytesRead != -1) {
            System.out.println("Read " + bytesRead);
            buffer.flip(); // 切换到读模式
            
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
            
            buffer.clear(); // 清空缓冲区,准备下次写入
            bytesRead = channel.read(buffer);
        }
        
        // 5. 写入数据
        String newData = "New String to write to file...\n";
        buffer.clear();
        buffer.put(newData.getBytes());
        buffer.flip();
        
        while (buffer.hasRemaining()) {
            channel.write(buffer);
        }
        
        // 6. 关闭资源
        channel.close();
        file.close();
    }
}

3. 缓冲区(Buffers)

缓冲区是NIO数据的容器,提供了比流更灵活的数据处理方式。

3.1 缓冲区类型

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

3.2 核心概念

  • capacity:缓冲区容量
  • position:当前位置
  • limit:限制位置
  • mark:标记位置,用于reset

3.3 代码示例:缓冲区操作

import java.nio.ByteBuffer;

public class BufferExample {
    public static void main(String[] args) {
        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        
        // 写入数据
        buffer.put((byte) 'H');
        buffer.put((byte) 'e');
        buffer.put((byte) 'l');
        buffer.put((byte) 'l');
        buffer.put((byte) 'o');
        
        System.out.println("初始状态: position=" + buffer.position() + 
                           ", limit=" + buffer.limit() + 
                           ", capacity=" + buffer.capacity());
        
        // 切换到读模式
        buffer.flip();
        System.out.println("flip后: position=" + buffer.position() + 
                           ", limit=" + buffer.limit());
        
        // 读取数据
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        System.out.println();
        
        // 重绕缓冲区
        buffer.rewind();
        System.out.println("rewind后: position=" + buffer.position());
        
        // 标记当前位置
        buffer.mark();
        
        // 读取部分数据
        buffer.get();
        buffer.get();
        System.out.println("读取两个字节后: position=" + buffer.position());
        
        // 重置到标记位置
        buffer.reset();
        System.out.println("reset后: position=" + buffer.position());
        
        // 清空缓冲区
        buffer.clear();
        System.out.println("clear后: position=" + buffer.position() + 
                           ", limit=" + buffer.limit());
    }
}

4. 选择器(Selectors)

选择器允许单个线程监控多个通道的I/O事件,如连接、数据到达等。

4.1 选择器工作原理

  1. 创建选择器
  2. 将通道注册到选择器
  3. 选择准备就绪的通道
  4. 处理就绪的通道

4.2 代码示例:NIO服务器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建服务器通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(8080));
        serverChannel.configureBlocking(false); // 设置为非阻塞模式
        
        // 2. 创建选择器
        Selector selector = Selector.open();
        
        // 3. 将服务器通道注册到选择器,监听接收连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("服务器已启动,监听端口 8080");
        
        // 4. 事件循环
        while (true) {
            // 阻塞等待事件,返回就绪事件数量
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            
            // 5. 获取就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove(); // 必须移除,否则会重复处理
                
                // 6. 处理不同类型的事件
                if (key.isAcceptable()) {
                    // 接受新连接
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel clientChannel = server.accept();
                    clientChannel.configureBlocking(false);
                    // 注册读事件
                    clientChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 读取客户端数据
                    SocketChannel clientChannel = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    
                    int bytesRead = clientChannel.read(buffer);
                    if (bytesRead == -1) {
                        // 客户端关闭连接
                        System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
                        clientChannel.close();
                    } else if (bytesRead > 0) {
                        // 处理数据
                        buffer.flip();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String message = new String(data);
                        System.out.println("收到消息: " + message);
                        
                        // 回显给客户端
                        buffer.clear();
                        buffer.put(("服务器收到: " + message).getBytes());
                        buffer.flip();
                        clientChannel.write(buffer);
                    }
                }
            }
        }
    }
}

4.3 客户端代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class NioClient {
    public static void main(String[] args) throws IOException {
        // 1. 创建客户端通道
        SocketChannel clientChannel = SocketChannel.open();
        clientChannel.connect(new InetSocketAddress("localhost", 8080));
        clientChannel.configureBlocking(false); // 非阻塞模式
        
        // 2. 准备发送数据
        String message = "Hello Server!";
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(message.getBytes());
        buffer.flip();
        
        // 3. 发送数据
        while (buffer.hasRemaining()) {
            clientChannel.write(buffer);
        }
        System.out.println("消息已发送到服务器");
        
        // 4. 接收响应
        buffer.clear();
        int bytesRead = clientChannel.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();
            byte[] response = new byte[buffer.remaining()];
            buffer.get(response);
            System.out.println("服务器响应: " + new String(response));
        }
        
        // 5. 关闭连接
        clientChannel.close();
    }
}

5. 非阻塞I/O

NIO的核心优势之一是支持非阻塞I/O操作,允许线程在等待I/O时处理其他任务。

5.1 阻塞与非阻塞模式

阻塞模式:I/O操作会阻塞线程,直到操作完成
非阻塞模式:I/O操作立即返回,不管是否完成

5.2 代码示例:非阻塞网络通信

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NonBlockingServer {
    public static void main(String[] args) throws IOException {
        // 1. 创建服务器通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.socket().bind(new InetSocketAddress(8081));
        serverChannel.configureBlocking(false); // 关键:设置为非阻塞模式
        
        // 2. 创建选择器
        Selector selector = Selector.open();
        
        // 3. 注册接受连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        System.out.println("非阻塞服务器启动,端口: 8081");
        
        // 4. 事件循环
        while (true) {
            // 非阻塞选择,立即返回
            int readyChannels = selector.selectNow();
            if (readyChannels == 0) {
                // 没有就绪通道,可以执行其他任务
                System.out.println("没有就绪通道,执行其他任务...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            
            // 处理就绪通道
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                
                try {
                    if (key.isAcceptable()) {
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = server.accept();
                        if (clientChannel != null) {
                            clientChannel.configureBlocking(false);
                            clientChannel.register(selector, SelectionKey.OP_READ);
                            System.out.println("新连接: " + clientChannel.getRemoteAddress());
                        }
                    } else if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(256);
                        
                        int bytesRead = clientChannel.read(buffer);
                        if (bytesRead == -1) {
                            System.out.println("客户端断开: " + clientChannel.getRemoteAddress());
                            clientChannel.close();
                            key.cancel();
                        } else if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.remaining()];
                            buffer.get(data);
                            String message = new String(data).trim();
                            System.out.println("收到消息: " + message);
                            
                            // 回显
                            buffer.clear();
                            buffer.put(("回显: " + message).getBytes());
                            buffer.flip();
                            clientChannel.write(buffer);
                        }
                    }
                } catch (IOException e) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException ioException) {
                        ioException.printStackTrace();
                    }
                }
            }
        }
    }
}

6. 文件操作(NIO.2)

Java 7引入的NIO.2提供了更强大的文件操作API。

6.1 Path和Files类

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.Stream;

public class Nio2FileExample {
    public static void main(String[] args) throws IOException {
        // 1. 创建Path对象
        Path path = Paths.get("/tmp/test.txt");
        System.out.println("路径: " + path);
        System.out.println("文件名: " + path.getFileName());
        System.out.println("父目录: " + path.getParent());
        
        // 2. 创建文件和目录
        Path dirPath = Paths.get("/tmp/nio2-example");
        Files.createDirectories(dirPath); // 创建多级目录
        System.out.println("目录创建: " + dirPath);
        
        Path filePath = dirPath.resolve("example.txt");
        if (!Files.exists(filePath)) {
            Files.createFile(filePath);
            System.out.println("文件创建: " + filePath);
        }
        
        // 3. 写入文件
        String content = "Hello NIO.2!\nThis is a test file.";
        Files.write(filePath, content.getBytes());
        System.out.println("文件写入完成");
        
        // 4. 读取文件
        byte[] fileContent = Files.readAllBytes(filePath);
        System.out.println("文件内容: " + new String(fileContent));
        
        // 5. 逐行读取
        System.out.println("\n逐行读取:");
        Files.lines(filePath).forEach(System.out::println);
        
        // 6. 文件属性
        BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
        System.out.println("\n文件属性:");
        System.out.println("创建时间: " + attrs.creationTime());
        System.out.println("最后修改时间: " + attrs.lastModifiedTime());
        System.out.println("大小: " + attrs.size() + " bytes");
        System.out.println("是否是目录: " + attrs.isDirectory());
        
        // 7. 复制/移动文件
        Path backupPath = dirPath.resolve("example-backup.txt");
        Files.copy(filePath, backupPath, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("文件备份完成: " + backupPath);
        
        // 8. 遍历目录
        System.out.println("\n目录内容:");
        try (Stream<Path> paths = Files.walk(dirPath)) {
            paths.forEach(p -> System.out.println(
                Files.isDirectory(p) ? "[DIR] " + p : "[FILE] " + p
            ));
        }
        
        // 9. 文件监听 (WatchService)
        watchDirectory(dirPath);
        
        // 10. 删除文件和目录
        // Files.delete(backupPath);
        // Files.delete(filePath);
        // Files.delete(dirPath);
    }
    
    private static void watchDirectory(Path dir) throws IOException {
        try (WatchService watcher = FileSystems.getDefault().newWatchService()) {
            dir.register(watcher, 
                StandardWatchEventKinds.ENTRY_CREATE,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_MODIFY);
            
            System.out.println("\n开始监听目录变化,按Enter键停止...");
            new Thread(() -> {
                while (true) {
                    try {
                        WatchKey key = watcher.take(); // 阻塞等待事件
                        
                        for (WatchEvent<?> event : key.pollEvents()) {
                            WatchEvent.Kind<?> kind = event.kind();
                            Path fileName = (Path) event.context();
                            
                            System.out.println(kind.name() + ": " + fileName);
                        }
                        
                        if (!key.reset()) {
                            break; // 监听器失效
                        }
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }).start();
            
            System.in.read(); // 等待用户输入
        }
    }
}

6.2 异步文件操作

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;

public class AsyncFileExample {
    public static void main(String[] args) throws Exception {
        Path path = Paths.get("/tmp/async-file.txt");
        
        // 1. 创建异步文件通道
        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                path, 
                StandardOpenOption.CREATE, 
                StandardOpenOption.WRITE, 
                StandardOpenOption.READ,
                StandardOpenOption.TRUNCATE_EXISTING)) {
            
            System.out.println("文件大小: " + fileChannel.size() + " bytes");
            
            // 2. 异步写入 - Future方式
            String content = "Hello Async NIO!";
            ByteBuffer buffer = ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8));
            
            Future<Integer> writeResult = fileChannel.write(buffer, 0);
            System.out.println("等待写入完成...");
            Integer bytesWritten = writeResult.get(); // 阻塞等待结果
            System.out.println("写入字节数: " + bytesWritten);
            
            // 3. 异步读取 - 回调方式
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            fileChannel.read(readBuffer, 0, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    System.out.println("异步读取完成,读取字节数: " + result);
                    attachment.flip();
                    byte[] data = new byte[attachment.remaining()];
                    attachment.get(data);
                    System.out.println("读取内容: " + new String(data, StandardCharsets.UTF_8));
                }
                
                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    System.err.println("异步读取失败: " + exc.getMessage());
                }
            });
            
            // 等待异步操作完成
            Thread.sleep(1000);
            
            // 4. 异步写入多个位置
            ByteBuffer buffer1 = ByteBuffer.wrap("First part\n".getBytes());
            ByteBuffer buffer2 = ByteBuffer.wrap("Second part\n".getBytes());
            
            fileChannel.write(buffer1, 0);
            fileChannel.write(buffer2, buffer1.capacity());
            
            System.out.println("多个异步写入操作已发起");
        }
    }
}

7. 异步网络编程(NIO.2)

NIO.2提供了真正的异步网络操作API。

7.1 异步TCP服务器

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;

public class AsyncTcpServer {
    private static final int PORT = 8082;
    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) throws Exception {
        // 1. 创建异步服务器通道
        try (AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()) {
            serverChannel.bind(new InetSocketAddress(PORT));
            System.out.println("异步服务器启动,监听端口: " + PORT);
            
            // 2. 准备接受连接
            final CountDownLatch latch = new CountDownLatch(1);
            
            serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
                @Override
                public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {
                    try {
                        System.out.println("新客户端连接: " + clientChannel.getRemoteAddress());
                        
                        // 准备下一次接受连接
                        serverChannel.accept(null, this);
                        
                        // 处理客户端请求
                        handleClient(clientChannel);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.err.println("接受连接失败: " + exc.getMessage());
                    latch.countDown();
                }
            });
            
            // 3. 保持服务器运行
            latch.await();
        }
    }
    
    private static void handleClient(AsynchronousSocketChannel clientChannel) {
        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
        
        // 异步读取客户端数据
        clientChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer attachment) {
                if (result == -1) {
                    try {
                        System.out.println("客户端断开连接: " + clientChannel.getRemoteAddress());
                        clientChannel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    return;
                }
                
                // 处理数据
                attachment.flip();
                byte[] data = new byte[attachment.remaining()];
                attachment.get(data);
                String message = new String(data).trim();
                System.out.println("收到消息: " + message);
                
                // 回显给客户端
                String response = "服务器收到: " + message;
                ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
                
                clientChannel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                    @Override
                    public void completed(Integer result, ByteBuffer attachment) {
                        if (attachment.hasRemaining()) {
                            // 继续写入剩余数据
                            clientChannel.write(attachment, attachment, this);
                        } else {
                            // 读取下一条消息
                            attachment.clear();
                            clientChannel.read(attachment, attachment, this);
                        }
                    }
                    
                    @Override
                    public void failed(Throwable exc, ByteBuffer attachment) {
                        try {
                            System.err.println("写入失败: " + exc.getMessage());
                            clientChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    System.err.println("读取失败: " + exc.getMessage());
                    clientChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

7.2 异步TCP客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class AsyncTcpClient {
    private static final String HOST = "localhost";
    private static final int PORT = 8082;
    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) throws Exception {
        // 1. 创建异步客户端通道
        try (AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open()) {
            // 2. 连接到服务器
            Future<Void> connectFuture = clientChannel.connect(new InetSocketAddress(HOST, PORT));
            connectFuture.get(5, TimeUnit.SECONDS); // 等待连接完成
            System.out.println("连接到服务器: " + HOST + ":" + PORT);
            
            // 3. 准备发送消息
            String message = "Hello Async Server!";
            ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
            
            // 4. 异步写入
            final CountDownLatch writeLatch = new CountDownLatch(1);
            
            clientChannel.write(writeBuffer, null, new CompletionHandler<Integer, Object>() {
                @Override
                public void completed(Integer result, Object attachment) {
                    System.out.println("发送字节数: " + result);
                    writeLatch.countDown();
                }
                
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.err.println("写入失败: " + exc.getMessage());
                    writeLatch.countDown();
                }
            });
            
            writeLatch.await(); // 等待写入完成
            
            // 5. 读取响应
            final CountDownLatch readLatch = new CountDownLatch(1);
            ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
            
            clientChannel.read(readBuffer, null, new CompletionHandler<Integer, Object>() {
                @Override
                public void completed(Integer result, Object attachment) {
                    if (result == -1) {
                        System.out.println("服务器关闭连接");
                        readLatch.countDown();
                        return;
                    }
                    
                    System.out.println("收到字节数: " + result);
                    readBuffer.flip();
                    byte[] data = new byte[readBuffer.remaining()];
                    readBuffer.get(data);
                    System.out.println("服务器响应: " + new String(data));
                    
                    readLatch.countDown();
                }
                
                @Override
                public void failed(Throwable exc, Object attachment) {
                    System.err.println("读取失败: " + exc.getMessage());
                    readLatch.countDown();
                }
            });
            
            readLatch.await(5, TimeUnit.SECONDS); // 等待读取完成,设置超时
        }
    }
}

8. 适用场景

8.1 高并发网络服务器

  • 即时通讯服务器:使用Selector管理成千上万的并发连接
  • 游戏服务器:低延迟要求的场景,NIO提供更好的性能
  • API网关:需要处理大量并发请求的中间层服务

8.2 高效文件操作

  • 大数据处理:使用FileChannel的内存映射文件提高大文件处理性能
  • 日志系统:高吞吐量的日志收集和处理
  • 文件同步/备份工具:利用NIO.2的异步和高级文件操作API

8.3 非阻塞I/O应用

  • 爬虫系统:同时监控多个网络连接
  • 消息队列:高吞吐量的消息处理
  • 代理服务器:需要同时处理多个客户端和后端连接

8.4 实时系统

  • 金融交易系统:低延迟要求的交易处理
  • 物联网平台:需要处理大量设备连接
  • 实时监控系统:持续收集和处理数据流

9. NIO与传统IO对比

特性传统IO (java.io)NIO (java.nio)NIO.2 (java.nio.file)
I/O模型阻塞式非阻塞式异步/非阻塞
控制单位流 (Stream)通道 (Channel) + 缓冲区 (Buffer)Path + Files工具类
并发处理一个连接一个线程一个线程多个连接 (Reactor模式)异步回调/Future
文件操作基本功能增强功能 (内存映射)完整文件系统API
选择器不支持支持 (Selector)不适用
性能一般高 (尤其在高并发)高 (文件操作)
使用难度简单中等中等到复杂

10. 最佳实践和注意事项

10.1 资源管理

  • 始终在finally块或try-with-resources中关闭通道
  • 及时取消不再需要的SelectionKey
  • 释放直接缓冲区(Direct Buffer)以避免内存泄漏

10.2 性能优化

  • 适当调整缓冲区大小,避免频繁的I/O操作
  • 使用直接缓冲区(Direct Buffer)提高I/O性能,但要权衡分配成本
  • 避免在选择器循环中执行耗时操作
  • 批量处理I/O事件,减少系统调用

10.3 错误处理

  • 正确处理IOException和异常情况
  • 实现超时机制,防止永久阻塞
  • 对于异步操作,确保有适当的错误回调
  • 处理连接重置和网络中断的情况

10.4 扩展性考虑

  • 考虑使用线程池处理I/O事件,避免单线程瓶颈
  • 采用分片策略,将连接分散到多个选择器
  • 为不同类型的操作(读/写/计算)使用不同的线程池
  • 实现背压机制,防止过载

10.5 内存管理

// 正确释放直接缓冲区
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

public class BufferUtils {
    private static Method cleanerMethod;
    private static Method cleanMethod;
    
    static {
        try {
            cleanerMethod = Class.forName("java.nio.DirectByteBuffer")
                .getMethod("cleaner");
            cleanerMethod.setAccessible(true);
            
            cleanMethod = Class.forName("sun.misc.Cleaner")
                .getMethod("clean");
        } catch (Exception e) {
            // 忽略异常
        }
    }
    
    public static void cleanDirectBuffer(ByteBuffer buffer) {
        if (buffer == null || !buffer.isDirect()) return;
        
        try {
            Object cleaner = cleanerMethod.invoke(buffer);
            if (cleaner != null) {
                cleanMethod.invoke(cleaner);
            }
        } catch (Exception e) {
            // 忽略异常
        }
    }
    
    // 使用示例
    public static void main(String[] args) {
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024 * 1024); // 1MB
        // 使用缓冲区...
        
        // 手动释放
        cleanDirectBuffer(directBuffer);
    }
}

11. 框架和库

现代Java应用通常不会直接使用NIO API,而是通过高级框架:

  • Netty:高性能异步事件驱动网络应用框架
  • Vert.x:用于构建响应式应用的工具包
  • Grizzly:NIO框架,GlassFish应用服务器的核心
  • Apache MINA:网络应用框架

这些框架封装了NIO的复杂性,提供了更高层次的抽象,同时保持了性能优势。

12. 结论

Java NIO提供了比传统IO更强大的功能,特别是在高并发、高性能场景下。通过通道、缓冲区和选择器的组合,NIO允许开发者构建可扩展的网络应用。NIO.2进一步增强了文件操作,并引入了真正的异步I/O。

选择NIO而非传统IO的情况:

  • 需要处理大量并发连接
  • 需要非阻塞I/O操作
  • 需要更高的文件操作性能
  • 需要更细粒度的I/O控制

尽管NIO比传统IO更复杂,但其在性能和可扩展性方面的优势使其成为现代高性能Java应用的首选。对于大多数应用场景,建议使用基于NIO的高级框架如Netty,而不是直接使用NIO API。