Java NIO(New I/O)是Java 1.4引入的一套新的I/O API,它提供了与标准I/O不同的工作方式,主要区别在于NIO采用了非阻塞和面向缓冲区的操作模式,能够更高效地处理大量并发连接。下面我将详细解释Java NIO的核心概念、组件和基本使用方法。
核心概念
Java NIO的核心由以下三个组件构成:
-
Channel(通道)
Channel是对传统I/O中流(Stream)的改进,它表示与实体(如文件、网络套接字)的连接,支持双向读写,且可以异步操作。常见的Channel实现有:FileChannel:用于文件操作SocketChannel:用于TCP客户端ServerSocketChannel:用于TCP服务器DatagramChannel:用于UDP通信
-
Buffer(缓冲区)
Buffer是NIO中数据的载体,所有数据都必须通过Buffer进行读写。Buffer本质上是一块内存区域,提供了高效的数据存取方法。常见的Buffer类型有:ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer
-
Selector(选择器)
Selector是Java NIO实现非阻塞I/O的关键组件,它允许一个线程同时监视多个Channel的I/O事件(如连接就绪、读就绪、写就绪等)。通过Selector,一个线程可以管理多个Channel,从而显著减少线程数量,降低系统开销。
Buffer的基本操作
Buffer有三个核心属性:
- capacity:缓冲区的容量,创建后不可变
- position:当前读写位置
- limit:读写操作的上限(读模式下为数据长度,写模式下为capacity)
Buffer的基本操作流程:
- 写入数据到Buffer
- 调用
flip()方法切换到读模式 - 从Buffer读取数据
- 调用
clear()或compact()方法切换回写模式
下面是一个简单的Buffer操作示例:
import java.nio.ByteBuffer;
public class BufferExample {
public static void main(String[] args) {
// 创建一个容量为10的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(10);
// 写入数据到Buffer
buffer.put((byte) 'H');
buffer.put((byte) 'e');
buffer.put((byte) 'l');
buffer.put((byte) 'l');
buffer.put((byte) 'o');
// 切换到读模式
buffer.flip();
// 从Buffer读取数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println();
// 切换回写模式
buffer.clear();
// 再次写入数据
buffer.put((byte) 'W');
buffer.put((byte) 'o');
buffer.put((byte) 'r');
buffer.put((byte) 'l');
buffer.put((byte) 'd');
// 再次切换到读模式
buffer.flip();
// 再次读取数据
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
System.out.println();
}
}
Channel的基本操作
下面是几种常见Channel的基本使用示例:
1. FileChannel示例(文件读写)
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelExample {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel()) {
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 从输入通道读取数据到缓冲区
while (inChannel.read(buffer) != -1) {
// 切换到读模式
buffer.flip();
// 将缓冲区数据写入输出通道
outChannel.write(buffer);
// 清空缓冲区,准备下次读取
buffer.clear();
}
System.out.println("文件复制完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. SocketChannel和ServerSocketChannel示例(网络通信)
服务器端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
public class NioServerExample {
public static void main(String[] args) {
try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
// 绑定端口
serverChannel.bind(new InetSocketAddress(8080));
System.out.println("服务器启动,监听端口8080");
// 等待客户端连接
SocketChannel clientChannel = serverChannel.accept();
System.out.println("客户端连接成功: " + clientChannel.getRemoteAddress());
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取客户端数据
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到客户端消息: " + new String(data));
}
// 向客户端发送响应
String response = "Hello, Client! 服务器已收到你的消息";
buffer.clear();
buffer.put(response.getBytes());
buffer.flip();
clientChannel.write(buffer);
// 关闭连接
clientChannel.close();
System.out.println("客户端连接已关闭");
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClientExample {
public static void main(String[] args) {
try (SocketChannel clientChannel = SocketChannel.open()) {
// 连接服务器
clientChannel.connect(new InetSocketAddress("localhost", 8080));
System.out.println("已连接到服务器");
// 发送消息到服务器
String message = "Hello, Server! 这是客户端发送的消息";
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put(message.getBytes());
buffer.flip();
clientChannel.write(buffer);
// 接收服务器响应
buffer.clear();
int bytesRead = clientChannel.read(buffer);
if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
System.out.println("收到服务器响应: " + new String(data));
}
System.out.println("客户端操作完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Selector的基本使用
Selector是Java NIO实现非阻塞I/O的核心组件,下面是一个简单的Selector使用示例:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class SelectorExample {
public static void main(String[] args) {
try (Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
// 配置服务器通道
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// 注册服务器通道到Selector,监听ACCEPT事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,监听端口8080");
while (true) {
// 等待就绪的通道
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// 获取所有就绪的SelectionKey
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// 处理新连接
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
System.out.println("新连接: " + client.getRemoteAddress());
// 配置客户端通道为非阻塞模式,并注册读事件
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
else if (key.isReadable()) {
// 处理读事件
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.remaining()];
buffer.get(data);
System.out.println("收到消息: " + new String(data) + " 来自: " + client.getRemoteAddress());
} else if (bytesRead == -1) {
// 客户端关闭连接
System.out.println("连接关闭: " + client.getRemoteAddress());
client.close();
}
}
// 移除已处理的key
keyIterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
NIO与传统I/O的对比
| 特性 | 传统I/O (BIO) | NIO (New I/O) |
|---|---|---|
| 操作模式 | 阻塞I/O | 非阻塞I/O |
| 数据处理 | 面向流(Stream) | 面向缓冲区(Buffer) |
| 通道方向 | 单向(输入流或输出流) | 双向(Channel) |
| 选择器支持 | 不支持 | 支持(Selector) |
| 适用场景 | 连接数少且I/O操作耗时较长 | 连接数多且I/O操作耗时较短 |
优势与应用场景
Java NIO的非阻塞I/O模型相比传统I/O有以下优势:
-
更少的线程开销:通过Selector,一个线程可以管理多个Channel,减少了线程数量,降低了线程上下文切换的开销。
-
更高的并发处理能力:适合处理大量并发连接(如HTTP服务器、聊天服务器等),因为它不需要为每个连接创建单独的线程。
-
双向数据传输:Channel支持双向读写,简化了数据处理流程。
NIO的典型应用场景包括:
- 高性能服务器(如Web服务器、数据库服务器)
- 实时通信系统(如即时通讯、游戏服务器)
- 需要处理大量并发连接的网络应用
通过理解和掌握Java NIO的核心概念和基本操作,可以开发出更高效、更具扩展性的网络应用程序。
Java NIO基础概念与操作详解
1054

被折叠的 条评论
为什么被折叠?



