哪些Java案例展示了NIO用法?

wen java案例 2

本文目录导读:

哪些Java案例展示了NIO用法?

  1. 网络通信类案例(核心:Selector + Channel)
  2. 文件操作类案例(核心:FileChannel + 零拷贝)
  3. 综合案例:结合 NIO + 多线程的 HTTP Server
  4. 总结:NIO 核心知识点在案例中的体现
  5. 学习建议

Java NIO(New I/O,即 Java 1.4 引入的非阻塞 I/O 模型)的核心在于通道(Channel)缓冲区(Buffer)选择器(Selector),它特别适合处理高并发、大文件传输和网络通信场景。

以下按网络通信文件操作两大类,给出几个经典的 Java NIO 案例,并附上核心代码逻辑说明。


网络通信类案例(核心:Selector + Channel)

这类案例是 NIO 最典型的应用,用于替代传统的 BIO(Blocking I/O)连接池模型。

简易多人聊天室(核心演示:非阻塞 + Selector)

功能:一个服务端同时处理多个客户端连接,客户端可以发送消息,服务端广播给所有人。

核心思想

  • 服务端使用 Selector 监听 OP_ACCEPT(新连接)和 OP_READ(数据到达)事件。
  • 所有客户端连接都注册到同一个 Selector 上,单线程即可处理大量连接。

关键代码片段(服务端):

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class NioChatServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    public void start() throws IOException {
        // 1. 打开 ServerSocketChannel 并绑定端口
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false); // 非阻塞模式
        // 2. 打开 Selector 并注册 ACCEPT 事件
        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("NIO 聊天服务器启动,端口:8080");
        while (true) {
            // 3. 选择就绪的事件(阻塞等待,直到有事件发生)
            selector.select();
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove(); // 必须移除,否则会重复处理
                if (key.isAcceptable()) {
                    handleAccept(key);
                } else if (key.isReadable()) {
                    handleRead(key);
                }
            }
        }
    }
    private void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        SocketChannel client = server.accept();
        client.configureBlocking(false);
        // 将新客户端注册到 Selector,监听 READ 事件
        client.register(selector, SelectionKey.OP_READ);
        System.out.println("新客户端连接:" + client.getRemoteAddress());
    }
    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int bytesRead = client.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();
            byte[] data = new byte[buffer.limit()];
            buffer.get(data);
            String msg = new String(data);
            System.out.println("收到消息:" + msg);
            // 广播给其他所有客户端
            broadcast(client, msg);
        } else {
            // 客户端断开连接
            client.close();
            System.out.println("客户端断开");
        }
    }
    private void broadcast(SocketChannel excludeClient, String msg) throws IOException {
        ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
        for (SelectionKey key : selector.keys()) {
            Channel target = key.channel();
            if (target instanceof SocketChannel && target != excludeClient) {
                ((SocketChannel) target).write(buffer);
                buffer.rewind(); // 重置 position 以便再次写入
            }
        }
    }
}

特点:单线程管理所有连接,适合连接数很大但活动连接较少的场景(如聊天、推送、网关)。


HTTP 代理服务器(实战:手写简单代理)

功能:接收客户端的 HTTP 请求,转发到目标服务器,再将响应返回给客户端,全部使用 NIO 实现。

核心:需要使用 Piperegister 分别在两个 Channel 上监听读写事件,处理数据转发。

简化逻辑

// 伪代码:通过两个 SelectionKey 管理 client 和 remote 之间的数据流转
SocketChannel clientChannel = ...;
SocketChannel remoteChannel = ...;
// 当 clientChannel 可读时,读取数据并写入 remoteChannel
// 当 remoteChannel 可读时,读取数据并写入 clientChannel
// 这两个读写动作都注册在同一个 Selector 上

文件操作类案例(核心:FileChannel + 零拷贝)

文件拷贝(MappedByteBuffer 大文件高效拷贝)

功能:使用 FileChannel.map() 将文件直接映射到内存(MappedByteBuffer),实现零拷贝(用户态与内核态之间数据不经过 JVM 堆),速度远快于传统 InputStream/OutputStream

代码

import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class FileCopyWithMappedByteBuffer {
    public static void copyFile(String src, String dest) throws Exception {
        RandomAccessFile srcFile = new RandomAccessFile(src, "r");
        RandomAccessFile destFile = new RandomAccessFile(dest, "rw");
        FileChannel srcChannel = srcFile.getChannel();
        FileChannel destChannel = destFile.getChannel();
        long fileSize = srcChannel.size();
        // 将源文件映射到内存
        MappedByteBuffer mappedByteBuffer = srcChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileSize);
        // 写入目标文件
        destChannel.write(mappedByteBuffer);
        srcChannel.close();
        destChannel.close();
        srcFile.close();
        destFile.close();
        System.out.println("文件拷贝完成(使用 MappedByteBuffer)");
    }
}

对比传统方式:直接 transferTo()transferFrom() 方法性能更高(也是零拷贝),但 MappedByteBuffer 可以让你在内存中直接操作文件内容。


文件分片传输(Chunked File Transfer)

案例背景:网络编程中,一个大的文件需要分成多个小块发送,或者从多个位置并发读取,NIO 的 FileChannel 支持位置 (position) 指定。

代码

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChunkReader {
    // 读取文件的指定片段
    public static byte[] readChunk(String path, long offset, int length) throws Exception {
        RandomAccessFile file = new RandomAccessFile(path, "r");
        FileChannel channel = file.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(length);
        // 关键:从指定位置开始读取
        channel.position(offset);
        channel.read(buffer);
        buffer.flip();
        byte[] data = new byte[buffer.limit()];
        buffer.get(data);
        channel.close();
        file.close();
        return data;
    }
}

实际应用:断点续传、分片下载、日志分段分析等。


非阻塞 Socket 读取文件示例(结合网络传输)

场景:客户端请求下载文件,服务端使用非阻塞 SocketChannel + FileChannel 发送文件内容。

关键点FileChannel 本身是阻塞的(文件系统天生没有非阻塞),所以发送文件时通常要用一个线程池处理读文件,然后将数据写入 SocketChannel

代码框架

// 服务端写数据到 SocketChannel(需要确保数据发送缓冲区有空闲)
public class FileSender implements Runnable {
    private SocketChannel socketChannel;
    private FileChannel fileChannel;
    // ... 初始化
    @Override
    public void run() {
        ByteBuffer buffer = ByteBuffer.allocate(8192);
        try {
            while (fileChannel.read(buffer) != -1) {
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

但注意:真正的生产者-消费者模式中,通常配合 Selector 的写事件处理,避免持续占用 CPU。


综合案例:结合 NIO + 多线程的 HTTP Server

  • 案例:基于 NIO 实现的简单 HTTP 解析 + 静态文件返回。
  • 特点
    • 主线程只负责 Selector.select() 分发事件。
    • 读写操作交给线程池处理(避免阻塞主线程)。
    • 解析 HTTP 请求头(Buffer 的 compact()flip() 操作)。
    • 返回静态 HTML/图片文件(利用 FileChannel.transferTo() 零拷贝发送)。

这类案例常用于理解 Netty 底层设计思想。


NIO 核心知识点在案例中的体现

案例 核心 NIO 组件 典型应用场景
聊天室 Selector, SocketChannel 高并发长连接、事件驱动
代理服务器 Selector, Pipe 网关、中间件
大文件拷贝 MappedByteBuffer, FileChannel 文件传输、备份
分片读取 FileChannel.position() 断点续传、分片下载
非阻塞文件发送 SocketChannel + FileChannel Web 服务器(NIO 版)

学习建议

  1. 从文件操作入手FileChannel 相对简单,无网络复杂性,容易理解 Buffer 和 Channel 的关系。
  2. 练习网络聊天室:这是理解 Selector 和事件驱动模型的最佳入门案例。
  3. 阅读 Netty 源码:Netty 是对 NIO 的封装,理解 NIO 后看 Netty 的 ChannelPipeline 和 EventLoop 会更容易。

如果你需要某个案例的完整可运行代码(如聊天室服务端 + 客户端),可以告诉我,我可以提供完整的类文件和测试方式。

上一篇Java案例如何实现限时抢购?

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!