本文目录导读:

下面我为你详细讲解Java NIO(New I/O)的完整案例,包含核心概念和实际代码示例。
Java NIO核心组件
Java NIO主要包含三大核心组件:
- Channel(通道):数据传输的载体
- Buffer(缓冲区):数据存储的容器
- Selector(选择器):多路复用器,监控多个通道
基础案例:文件复制
1 使用FileChannel复制文件
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelDemo {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try {
copyFile(sourceFile, targetFile);
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 使用FileChannel复制文件
*/
public static void copyFile(String source, String target) throws IOException {
// 打开文件通道
Path sourcePath = Paths.get(source);
Path targetPath = Paths.get(target);
try (FileChannel sourceChannel = FileChannel.open(sourcePath, StandardOpenOption.READ);
FileChannel targetChannel = FileChannel.open(targetPath, StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
// 创建缓冲区(大小为8KB)
ByteBuffer buffer = ByteBuffer.allocate(8192);
// 从源通道读取数据到缓冲区
while (sourceChannel.read(buffer) != -1) {
// 切换为读模式
buffer.flip();
// 将缓冲区数据写入目标通道
while (buffer.hasRemaining()) {
targetChannel.write(buffer);
}
// 清空缓冲区,准备下一次读取
buffer.clear();
}
}
}
}
2 使用transferTo实现零拷贝
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class ZeroCopyDemo {
public static void main(String[] args) throws IOException {
String sourceFile = "large_file.zip";
String targetFile = "large_file_copy.zip";
// 零拷贝方式复制文件
try (FileChannel sourceChannel = FileChannel.open(Paths.get(sourceFile), StandardOpenOption.READ);
FileChannel targetChannel = FileChannel.open(Paths.get(targetFile), StandardOpenOption.WRITE,
StandardOpenOption.CREATE)) {
long position = 0;
long size = sourceChannel.size();
// 使用transferTo实现零拷贝
while (position < size) {
position += sourceChannel.transferTo(position, size - position, targetChannel);
}
System.out.println("零拷贝复制完成!");
}
}
}
Socket通信案例
1 阻塞式Socket通信
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BlockingSocketDemo {
// 服务器端
public static class Server {
public static void main(String[] args) throws IOException {
int port = 8080;
// 创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
System.out.println("服务器启动,监听端口:" + port);
// 创建线程池处理客户端请求
ExecutorService executor = Executors.newFixedThreadPool(10);
while (true) {
// 接受客户端连接(阻塞)
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("客户端连接:" + socketChannel.getRemoteAddress());
// 为每个客户端创建独立线程
executor.submit(() -> handleClient(socketChannel));
}
}
private static void handleClient(SocketChannel socketChannel) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
// 读取客户端发送的数据
while (socketChannel.read(buffer) != -1) {
buffer.flip();
// 将数据转换字符串并打印
String message = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到客户端消息:" + message);
// 回显消息给客户端
String response = "服务器已收到:" + message;
buffer.clear();
buffer.put(response.getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 客户端
public static class Client {
public static void main(String[] args) throws IOException {
// 连接服务器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 8080));
// 发送多条消息
String[] messages = {"Hello", "World", "Java NIO"};
for (String message : messages) {
// 发送消息
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(message.getBytes());
buffer.flip();
socketChannel.write(buffer);
// 读取响应
buffer.clear();
socketChannel.read(buffer);
buffer.flip();
String response = new String(buffer.array(), 0, buffer.limit());
System.out.println("服务器响应:" + response);
Thread.sleep(1000);
}
socketChannel.close();
}
}
}
2 非阻塞式Socket通信(Selector多路复用)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class NonBlockingSocketDemo {
public static void main(String[] args) throws IOException {
int port = 8090;
Selector selector = Selector.open();
// 创建ServerSocketChannel并设置为非阻塞
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(port));
// 注册接受连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("非阻塞式服务器启动,端口:" + port);
// 存储客户端数据处理任务
ConcurrentHashMap<SocketChannel, StringBuilder> clientData = new ConcurrentHashMap<>();
while (true) {
// 阻塞直到有事件发生
selector.select();
// 获取所有事件
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
// 处理不同的事件
if (key.isAcceptable()) {
// 接受连接
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 注册读事件
client.register(selector, SelectionKey.OP_READ);
System.out.println("客户端连接:" + client.getRemoteAddress());
clientData.put(client, new StringBuilder());
} else if (key.isReadable()) {
// 读取数据
handleRead(key, clientData);
}
}
}
}
private static void handleRead(SelectionKey key,
ConcurrentHashMap<SocketChannel, StringBuilder> clientData) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
// 连接关闭
clientData.remove(client);
client.close();
key.cancel();
return;
}
buffer.flip();
String data = new String(buffer.array(), 0, buffer.limit());
System.out.println("收到数据:" + data);
// 处理数据
if (data.startsWith("START:")) {
String fileName = data.substring(6);
clientData.get(client).append("文件开始接收:" + fileName + "\n");
// 发送确认
sendResponse(client, "文件传输开始");
} else if (data.equals("END")) {
clientData.get(client).append("文件传输结束");
String fullData = clientData.get(client).toString();
System.out.println("完整文件数据:\n" + fullData);
clientData.put(client, new StringBuilder());
} else {
clientData.get(client).append(data);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendResponse(SocketChannel client, String message) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(message.getBytes());
buffer.flip();
client.write(buffer);
}
}
文件映射和内存映射
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class MappedFileDemo {
public static void main(String[] args) throws IOException {
// 创建文件
Path filePath = Paths.get("mapped_file.txt");
Files.deleteIfExists(filePath);
Files.createFile(filePath);
// 内存映射写入
writeWithMapping(filePath);
// 内存映射读取
readWithMapping(filePath);
}
/**
* 使用内存映射写入文件
*/
public static void writeWithMapping(Path filePath) throws IOException {
try (FileChannel channel = FileChannel.open(filePath,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
// 映射到内存(大小为文件大小或更大)
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
// 写入数据
String content = "这是一个内存映射文件示例\n";
buffer.put(content.getBytes());
buffer.put("Java NIO内存映射".getBytes());
System.out.println("内存映射写入完成");
}
}
/**
* 使用内存映射读取文件
*/
public static void readWithMapping(Path filePath) throws IOException {
try (FileChannel channel = FileChannel.open(filePath, StandardOpenOption.READ)) {
// 映射到只读模式
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 读取数据
byte[] data = new byte[buffer.limit()];
buffer.get(data);
String content = new String(data);
System.out.println("读取内容:\n" + content);
}
}
}
完整示例:NIO文件上传下载系统
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class FileTransferSystem {
// 服务器端
public static class FileServer {
private static Map<SocketChannel, FileTransferState> transfers = new HashMap<>();
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8081));
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("文件服务器启动...");
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
// 处理连接请求
if (key.isAcceptable()) {
handleAccept(key);
}
// 处理读取事件
if (key.isReadable()) {
handleRead(key);
}
}
}
}
private static void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
// 初始化传输状态
FileTransferState state = new FileTransferState();
transfers.put(client, state);
System.out.println("客户端连接:" + client.getRemoteAddress());
}
private static void handleRead(SelectionKey key) {
SocketChannel client = (SocketChannel) key.channel();
FileTransferState state = transfers.get(client);
ByteBuffer buffer = ByteBuffer.allocate(8192);
try {
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
// 连接关闭
transfers.remove(client);
client.close();
key.cancel();
return;
}
buffer.flip();
// 处理收到的数据
while (buffer.hasRemaining()) {
state.processData(buffer);
}
} catch (IOException e) {
try {
client.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
// 文件传输状态管理器
static class FileTransferState {
private String fileName;
private long fileSize;
private long receivedSize;
private Path outputPath;
private FileChannel outputChannel;
private boolean isReceiving;
public void processData(ByteBuffer buffer) throws IOException {
// 如果还没有收到文件名,首先解析文件名
if (!isReceiving) {
byte[] header = new byte[Math.min(buffer.limit(), 256)];
buffer.get(header, 0, header.length);
String headerStr = new String(header).trim();
if (headerStr.contains(":")) {
fileName = headerStr.split(":")[0];
fileSize = Long.parseLong(headerStr.split(":")[1]);
receivedSize = 0;
// 创建输出文件
outputPath = Paths.get("uploads", fileName);
Files.createDirectories(outputPath.getParent());
outputChannel = FileChannel.open(outputPath,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
isReceiving = true;
System.out.println("开始接收文件:" + fileName + ",大小:" + fileSize);
}
}
// 接收文件数据
if (isReceiving && buffer.hasRemaining()) {
outputChannel.write(buffer);
receivedSize += buffer.limit();
// 检查是否接收完成
if (receivedSize >= fileSize) {
outputChannel.close();
System.out.println("文件接收完成:" + fileName);
isReceiving = false;
}
}
}
}
}
// 客户端
public static class FileClient {
public static void main(String[] args) {
String serverIp = "localhost";
int serverPort = 8081;
String filePath = "test.txt";
try {
sendFile(serverIp, serverPort, filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void sendFile(String serverIp, int serverPort, String filePath) throws IOException {
// 连接服务器
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(serverIp, serverPort));
// 打开文件
Path file = Paths.get(filePath);
long fileSize = Files.size(file);
FileChannel fileChannel = FileChannel.open(file, StandardOpenOption.READ);
// 发送文件头信息
String header = file.getFileName() + ":" + fileSize;
ByteBuffer headerBuffer = ByteBuffer.wrap(header.getBytes());
socketChannel.write(headerBuffer);
// 发送文件数据
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (fileChannel.read(buffer) != -1) {
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
}
// 等待服务器处理
Thread.sleep(1000);
fileChannel.close();
socketChannel.close();
System.out.println("文件发送完成");
}
}
}
最佳实践建议
1 缓冲区管理
public class BufferManagementDemo {
// 动态调整缓冲区大小
public static ByteBuffer createOptimalBuffer(long fileSize) {
int optimalSize = 8192;
if (fileSize < 1024) {
optimalSize = 1024;
} else if (fileSize < 1024 * 1024) {
optimalSize = 8192;
} else if (fileSize < 1024 * 1024 * 100) {
optimalSize = 65536;
} else {
optimalSize = 1048576; // 1MB for large files
}
return ByteBuffer.allocate(optimalSize);
}
// 使用直接缓冲区提高性能
public static ByteBuffer createDirectBuffer(int size) {
return ByteBuffer.allocateDirect(size);
}
// 切片和复制
public static void bufferOperations() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 创建切片
ByteBuffer slice = buffer.slice();
// 复制(只复制内容和位置,不共享数据)
ByteBuffer duplicate = buffer.duplicate();
// 读取指定位置数据
buffer.position(100);
byte[] data = new byte[50];
buffer.get(data);
}
}
2 异常处理和资源管理
public class NIOExceptionHandling {
public static void safeChannelOperation() {
FileChannel channel = null;
try {
channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ);
// 操作通道
} catch (IOException e) {
System.err.println("IO操作失败:" + e.getMessage());
e.printStackTrace();
} finally {
// 确保资源关闭
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 推荐使用try-with-resources
public static void tryWithResourcesDemo() {
try (FileChannel channel = FileChannel.open(Paths.get("test.txt"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
} catch (IOException e) {
System.err.println("IO操作失败:" + e.getMessage());
}
}
}
性能对比
| 操作类型 | 传统IO | Java NIO | 适用场景 |
|---|---|---|---|
| 文件复制 | 逐字节/逐行 | 缓冲区+零拷贝 | 大文件处理 |
| Socket通信 | 阻塞式 | 非阻塞+多路复用 | 高并发服务器 |
| 文件操作 | 大量系统调用 | 内存映射 | 文件处理 |
| 缓冲区 | 自动管理 | 手动控制 | 需要精细控制性能 |
Java NIO相比传统IO提供了:
- 非阻塞模式:支持高并发
- 缓冲区操作:更细粒度的控制
- Selector多路复用:单线程处理多个连接
- 零拷贝技术:提高大文件传输性能
在实际应用中,需要根据具体场景选择合适的方案:
- 简单文件操作:传统IO
- 高并发网络服务:NIO + Selector
- 大文件处理:内存映射
- 频繁IO操作:NIO缓冲区