如何在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 使用注意事项
- 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。

浙公网安备 33010602011771号