如何在Java中使用NIO框架?

在 Java 中使用 NIO 框架(Java NIO)需掌握其核心组件(Buffer、Channel、Selector)及非阻塞 I/O 模型。以下从基础组件使用文件操作网络通信高级封装框架四个维度,结合示例详解具体用法:

一、Java NIO 核心组件基础

Java NIO 的核心是Buffer(缓冲区)Channel(通道)Selector(选择器),三者协同实现非阻塞 I/O:
  • Buffer:数据容器,用于存储待读写的数据(如 ByteBuffer、CharBuffer);
  • Channel:双向数据通道(连接数据源与 Buffer),支持非阻塞操作(如 FileChannel、SocketChannel);
  • Selector:多路复用器,让单线程监听多个 Channel 的 I/O 事件(如连接、可读、可写)。

二、NIO 文件操作(FileChannel)

FileChannel 用于文件的读写,支持阻塞 / 非阻塞模式,性能优于传统 FileInputStream/FileOutputStream。

1. 读取文件

java
 
运行
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

public class NIOFileReadExample {
    public static void main(String[] args) {
        try (
            // 以读写模式打开文件
            RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
            // 获取文件通道
            FileChannel channel = file.getChannel()
        ) {
            // 分配缓冲区(容量1024字节)
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            // 从通道读取数据到缓冲区
            int bytesRead = channel.read(buffer);
            while (bytesRead != -1) { // -1表示读取完毕
                // 切换缓冲区为"读模式"(limit=position,position=0)
                buffer.flip();
                
                // 读取缓冲区数据(转换为字符串)
                String content = StandardCharsets.UTF_8.decode(buffer).toString();
                System.out.print(content);
                
                // 清空缓冲区(切换为"写模式",position=0,limit=capacity)
                buffer.clear();
                // 继续读取下一批数据
                bytesRead = channel.read(buffer);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

2. 写入文件

java
 
运行
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

public class NIOFileWriteExample {
    public static void main(String[] args) {
        try (
            RandomAccessFile file = new RandomAccessFile("output.txt", "rw");
            FileChannel channel = file.getChannel()
        ) {
            String data = "Hello Java NIO!";
            // 将字符串转为字节数组,写入缓冲区
            ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));
            
            // 从缓冲区写入通道(即写入文件)
            channel.write(buffer);
            System.out.println("数据写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

3. 文件复制(通道直接传输)

FileChannel 支持transferFrom()/transferTo()直接传输数据,无需缓冲区中转,效率更高:
java
 
运行
 
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class NIOFileCopyExample {
    public static void main(String[] args) {
        try (
            RandomAccessFile sourceFile = new RandomAccessFile("source.txt", "r");
            RandomAccessFile targetFile = new RandomAccessFile("target.txt", "rw");
            FileChannel sourceChannel = sourceFile.getChannel();
            FileChannel targetChannel = targetFile.getChannel()
        ) {
            // 从源通道传输数据到目标通道(参数:起始位置,传输大小,目标通道)
            sourceChannel.transferTo(0, sourceChannel.size(), targetChannel);
            System.out.println("文件复制完成");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 

三、NIO 网络通信(Selector + SocketChannel)

Java NIO 的核心优势是非阻塞网络通信,通过 Selector 实现单线程处理多连接,适用于高并发场景(如服务器开发)。

1. NIO 服务器端(支持多客户端连接)

java
 
运行
 
 
 
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) {
        try (
            // 打开Selector(多路复用器)
            Selector selector = Selector.open();
            // 打开服务器通道(ServerSocketChannel)
            ServerSocketChannel serverChannel = ServerSocketChannel.open()
        ) {
            // 绑定端口
            serverChannel.bind(new InetSocketAddress(8080));
            // 设置为非阻塞模式(必须,否则Selector无法工作)
            serverChannel.configureBlocking(false);
            // 注册"连接事件"(OP_ACCEPT)到Selector
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            
            System.out.println("NIO服务器启动,监听端口8080...");
            
            while (true) {
                // 阻塞等待事件触发(返回触发的事件数)
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;
                
                // 获取所有触发的事件Key
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
                
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    
                    // 处理"连接事件"(客户端发起连接)
                    if (key.isAcceptable()) {
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        // 接受客户端连接,返回SocketChannel
                        SocketChannel clientChannel = ssc.accept();
                        clientChannel.configureBlocking(false);
                        // 注册"读事件"(OP_READ)到Selector,关联一个缓冲区
                        clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                        System.out.println("客户端连接:" + clientChannel.getRemoteAddress());
                    }
                    
                    // 处理"读事件"(客户端发送数据)
                    else if (key.isReadable()) {
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        // 获取关联的缓冲区
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        // 从通道读取数据到缓冲区
                        int bytesRead = clientChannel.read(buffer);
                        
                        if (bytesRead == -1) {
                            // 客户端关闭连接
                            clientChannel.close();
                            key.cancel();
                            System.out.println("客户端断开连接:" + clientChannel.getRemoteAddress());
                            continue;
                        }
                        
                        // 切换缓冲区为读模式
                        buffer.flip();
                        // 解码数据为字符串
                        String msg = StandardCharsets.UTF_8.decode(buffer).toString().trim();
                        System.out.println("收到客户端消息:" + msg);
                        
                        // 回复客户端(可选)
                        String response = "服务器已收到:" + msg;
                        buffer.clear();
                        buffer.put(response.getBytes(StandardCharsets.UTF_8));
                        buffer.flip();
                        clientChannel.write(buffer);
                        
                        // 清空缓冲区,准备下次读取
                        buffer.clear();
                    }
                    
                    // 移除已处理的事件Key(必须,否则会重复处理)
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

2. NIO 客户端(连接服务器并发送数据)

java
 
运行
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class NIOClient {
    public static void main(String[] args) {
        try (
            // 打开SocketChannel
            SocketChannel clientChannel = SocketChannel.open();
            Scanner scanner = new Scanner(System.in)
        ) {
            // 连接服务器
            clientChannel.connect(new InetSocketAddress("localhost", 8080));
            // 设置为非阻塞模式(可选,若需异步操作)
            clientChannel.configureBlocking(false);
            
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            System.out.println("已连接服务器,输入消息发送(输入exit退出):");
            
            while (true) {
                String msg = scanner.nextLine();
                if ("exit".equalsIgnoreCase(msg)) break;
                
                // 写入数据到缓冲区
                buffer.put(msg.getBytes(StandardCharsets.UTF_8));
                buffer.flip();
                // 发送数据到服务器
                clientChannel.write(buffer);
                buffer.clear();
                
                // 读取服务器回复(非阻塞模式需循环读取)
                int bytesRead = clientChannel.read(buffer);
                if (bytesRead > 0) {
                    buffer.flip();
                    String response = StandardCharsets.UTF_8.decode(buffer).toString();
                    System.out.println("服务器回复:" + response);
                    buffer.clear();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 

四、使用高级 NIO 封装框架(Netty)

Java NIO 原生 API 较繁琐,实际开发中常使用Netty(基于 NIO 封装的高性能网络框架),简化开发流程:

1. Netty 服务器端示例

java
 
运行
 
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 主线程组(接受连接)
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 工作线程组(处理读写)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // 使用NIO通道
                    .option(ChannelOption.SO_BACKLOG, 128) // 连接队列大小
                    .childOption(ChannelOption.SO_KEEPALIVE, true) // 保持长连接
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // 添加字符串编解码器
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            // 添加自定义业务处理器
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });
            
            // 绑定端口并启动
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("Netty服务器启动,监听端口8080...");
            
            // 等待服务器关闭
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    // 自定义业务处理器
    static class NettyServerHandler extends SimpleChannelInboundHandler<String> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println("收到客户端消息:" + msg);
            // 回复客户端
            ctx.writeAndFlush("服务器已收到:" + msg);
        }
        
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("客户端连接:" + ctx.channel().remoteAddress());
        }
        
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }
}
 

2. Netty 客户端示例

java
 
运行
 
 
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            
            // 连接服务器
            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            Channel channel = future.channel();
            
            System.out.println("已连接服务器,输入消息发送(输入exit退出):");
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String msg = scanner.nextLine();
                if ("exit".equalsIgnoreCase(msg)) {
                    channel.close();
                    break;
                }
                // 发送消息
                channel.writeAndFlush(msg);
            }
            
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
    
    static class NettyClientHandler extends SimpleChannelInboundHandler<String> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
            System.out.println("服务器回复:" + msg);
        }
    }
}
 

五、NIO 使用注意事项

  1. Buffer 操作:务必区分 “写模式” 和 “读模式”(通过flip()切换),避免数据读取错误;
  2. 非阻塞模式:Channel 需设置为configureBlocking(false)才能配合 Selector 使用;
  3. Selector 事件处理:处理完事件后必须移除SelectionKeykeyIterator.remove()),否则会重复触发;
  4. 资源释放:Channel、Selector 需通过try-with-resources或手动关闭,避免资源泄漏;
  5. 高并发场景:优先使用 Netty 等封装框架,避免原生 NIO 的复杂细节。

总结

Java NIO 通过 Buffer、Channel、Selector 实现非阻塞 I/O,适用于文件操作和高并发网络通信。原生 API 需手动管理缓冲区和事件,而 Netty 等框架封装了底层细节,大幅提升开发效率。根据场景选择:简单文件操作可用原生 FileChannel,高并发网络开发优先用 Netty。
posted @ 2025-11-27 18:55  炖猪脚  阅读(2)  评论(0)    收藏  举报