在 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)到SelectorserverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO服务器启动,监听端口8080...");while (true) {// 阻塞等待事件触发(返回触发的事件数)int readyChannels = selector.select();if (readyChannels == 0) continue;// 获取所有触发的事件KeySet<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();// 处理"连接事件"(客户端发起连接)if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();// 接受客户端连接,返回SocketChannelSocketChannel 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 (// 打开SocketChannelSocketChannel 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>() {@Overrideprotected 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> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("收到客户端消息:" + msg);// 回复客户端ctx.writeAndFlush("服务器已收到:" + msg);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("客户端连接:" + ctx.channel().remoteAddress());}@Overridepublic 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>() {@Overrideprotected 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> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("服务器回复:" + msg);}}
}
五、NIO 使用注意事项
- Buffer 操作:务必区分 “写模式” 和 “读模式”(通过
flip()切换),避免数据读取错误; - 非阻塞模式:Channel 需设置为
configureBlocking(false)才能配合 Selector 使用; - Selector 事件处理:处理完事件后必须移除
SelectionKey(keyIterator.remove()),否则会重复触发; - 资源释放:Channel、Selector 需通过
try-with-resources或手动关闭,避免资源泄漏; - 高并发场景:优先使用 Netty 等封装框架,避免原生 NIO 的复杂细节。
总结
Java NIO 通过 Buffer、Channel、Selector 实现非阻塞 I/O,适用于文件操作和高并发网络通信。原生 API 需手动管理缓冲区和事件,而 Netty 等框架封装了底层细节,大幅提升开发效率。根据场景选择:简单文件操作可用原生 FileChannel,高并发网络开发优先用 Netty。